From e3c29f0c64cbf162928212ead5ae4a02e3c101b1 Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Tue, 23 Jun 2020 00:12:09 -0700 Subject: [PATCH 01/36] Test to check for document CRUD with MultiHash Kind. --- Microsoft.Azure.Cosmos/src/PartitionKey.cs | 10 ++++++++ .../NameRoutingTests.cs | 25 +++++++++++++++++++ .../Utils/TestCommon.cs | 24 ++++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/Microsoft.Azure.Cosmos/src/PartitionKey.cs b/Microsoft.Azure.Cosmos/src/PartitionKey.cs index 87490dc191..4280d8b57a 100644 --- a/Microsoft.Azure.Cosmos/src/PartitionKey.cs +++ b/Microsoft.Azure.Cosmos/src/PartitionKey.cs @@ -95,6 +95,16 @@ internal PartitionKey(object value) this.IsNone = false; } + /// + /// Creates a new partition key value. + /// + /// The value to use as partition key. + internal PartitionKey(object[] value) + { + this.InternalKey = new Documents.PartitionKey(value).InternalKey; + this.IsNone = false; + } + /// /// Creates a new partition key value. /// diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs index 9bdedf3812..eecc211dd6 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs @@ -1786,6 +1786,31 @@ public async Task TestScriptCreateOnContainerRecreateFromDifferentPartitionKeyPa await this.TestScriptCreateOnContainerRecreateFromDifferentPartitionKeyPath(TestCommon.CreateCosmosClient(true)); } + [TestMethod] + public async Task VerifyDocumentCrudWithMultiHashKind() + { + DocumentClient client = TestCommon.CreateClient(false); + await client.CreateDatabaseAsync(new Database { Id = "db1" }); + PartitionKeyDefinition partitionKeyDefinition1 = new PartitionKeyDefinition { Paths = new System.Collections.ObjectModel.Collection(new[] { "/pKey", "/Id" }), Kind = PartitionKind.MultiHash }; + DocumentCollection coll1 = await TestCommon.CreateCollectionAsync(client, "/dbs/db1", new DocumentCollection { Id = "coll1", PartitionKey = partitionKeyDefinition1 }); + + DocumentCollection collTemp1 = await client.ReadDocumentCollectionAsync(UriFactory.CreateDocumentCollectionUri("db1", "coll1")); + Assert.AreEqual(collTemp1, coll1); + + PartitionKeyDefinition partitionKeyDefinition2 = new PartitionKeyDefinition { Paths = new System.Collections.ObjectModel.Collection(new[] {"/Id" }), Kind = PartitionKind.MultiHash }; + DocumentCollection coll2 = await TestCommon.CreateCollectionAsync(client, "/dbs/db1", new DocumentCollection { Id = "coll2", PartitionKey = partitionKeyDefinition2 }); + + DocumentCollection collTemp2 = await client.ReadDocumentCollectionAsync(UriFactory.CreateDocumentCollectionUri("db1", "coll2")); + Assert.AreEqual(collTemp2, coll2); + + PartitionKeyDefinition partitionKeyDefinition3 = new PartitionKeyDefinition { Paths = new System.Collections.ObjectModel.Collection(new[] { "/pKey", "/Id", "/Name" }), Kind = PartitionKind.MultiHash }; + DocumentCollection coll3 = await TestCommon.CreateCollectionAsync(client, "/dbs/db1", new DocumentCollection { Id = "coll3", PartitionKey = partitionKeyDefinition3 }); + + DocumentCollection collTemp3 = await client.ReadDocumentCollectionAsync(UriFactory.CreateDocumentCollectionUri("db1", "coll3")); + Assert.AreEqual(collTemp3, coll3); + + } + internal async Task TestScriptCreateOnContainerRecreateFromDifferentPartitionKeyPath(CosmosClient client) { await TestCommon.DeleteAllDatabasesAsync(); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/TestCommon.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/TestCommon.cs index 231fa94907..fbb8cef270 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/TestCommon.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/TestCommon.cs @@ -627,6 +627,30 @@ internal static DocumentCollection CreateOrGetDocumentCollection(DocumentClient return client.Read(documentCollections[0].ResourceId); } + internal static DocumentCollection CreateOrGetDocumentCollectionWithMultiHash(DocumentClient client, out Database database) + { + database = TestCommon.CreateOrGetDatabase(client); + + IList documentCollections = TestCommon.ListAll( + client, + database.ResourceId); + + if (documentCollections.Count == 0) + { + PartitionKeyDefinition partitionKeyDefinition = new PartitionKeyDefinition { Paths = new System.Collections.ObjectModel.Collection(new[] { "/pk","/key" }), Kind = PartitionKind.MultiHash }; + DocumentCollection documentCollection1 = new DocumentCollection + { + Id = Guid.NewGuid().ToString("N"), + PartitionKey = partitionKeyDefinition + }; + + return TestCommon.CreateCollectionAsync(client, database, documentCollection1, + new RequestOptions() { OfferThroughput = 10000 }).Result; + } + + return client.Read(documentCollections[0].ResourceId); + } + internal static Document CreateOrGetDocument(DocumentClient client) { From 409636009bd9347877f9369db71ced2241908e87 Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Tue, 23 Jun 2020 08:46:13 -0700 Subject: [PATCH 02/36] Test update. --- .../NameRoutingTests.cs | 93 +++++++++++++++---- 1 file changed, 77 insertions(+), 16 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs index eecc211dd6..bdc7442e96 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs @@ -1789,25 +1789,86 @@ public async Task TestScriptCreateOnContainerRecreateFromDifferentPartitionKeyPa [TestMethod] public async Task VerifyDocumentCrudWithMultiHashKind() { - DocumentClient client = TestCommon.CreateClient(false); - await client.CreateDatabaseAsync(new Database { Id = "db1" }); - PartitionKeyDefinition partitionKeyDefinition1 = new PartitionKeyDefinition { Paths = new System.Collections.ObjectModel.Collection(new[] { "/pKey", "/Id" }), Kind = PartitionKind.MultiHash }; - DocumentCollection coll1 = await TestCommon.CreateCollectionAsync(client, "/dbs/db1", new DocumentCollection { Id = "coll1", PartitionKey = partitionKeyDefinition1 }); - - DocumentCollection collTemp1 = await client.ReadDocumentCollectionAsync(UriFactory.CreateDocumentCollectionUri("db1", "coll1")); - Assert.AreEqual(collTemp1, coll1); - - PartitionKeyDefinition partitionKeyDefinition2 = new PartitionKeyDefinition { Paths = new System.Collections.ObjectModel.Collection(new[] {"/Id" }), Kind = PartitionKind.MultiHash }; - DocumentCollection coll2 = await TestCommon.CreateCollectionAsync(client, "/dbs/db1", new DocumentCollection { Id = "coll2", PartitionKey = partitionKeyDefinition2 }); + DocumentClient client = TestCommon.CreateClient(true); + Database database = await client.CreateDatabaseAsync(new Database { Id = "mydb" }); + try + { + TestCommon.SetBooleanConfigurationProperty("enableSubPartitioning", true); + DocumentCollection collection = await TestCommon.CreateCollectionAsync(client, + "/dbs/mydb", + new DocumentCollection + { + Id = "mycoll", + PartitionKey = new PartitionKeyDefinition + { + Paths = new Collection { "/ZipCode", "/Address" }, + Kind = PartitionKind.MultiHash, + Version = PartitionKeyDefinitionVersion.V2 + } + }); + + //Document create. + ResourceResponse[] documents = new ResourceResponse[3]; + Document doc1 = new Document { Id = "document1" }; + doc1.SetValue("ZipCode", "500026"); + doc1.SetValue("Address", "Secunderabad"); + documents[0] = await client.CreateDocumentAsync("/dbs/mydb/colls/mycoll", doc1); + + doc1 = new Document { Id = "document2" }; + doc1.SetValue("ZipCode", "15232"); + doc1.SetValue("Address", "Pittsburgh"); + documents[1] = await client.CreateDocumentAsync("/dbs/mydb/colls/mycoll", doc1); + + doc1 = new Document { Id = "document3" }; + doc1.SetValue("ZipCode", "11790"); + doc1.SetValue("Address", "Stonybrook"); + documents[2] = await client.CreateDocumentAsync("/dbs/mydb/colls/mycoll", doc1); + + Assert.AreEqual(3, documents.Select(document => ((Document)document).SelfLink).Distinct().Count()); + + //Document Read. + foreach (Document document in documents) + { + PartitionKey pKey = new PartitionKey(new object[] { document.GetValue("ZipCode"), document.GetPropertyValue("Address") }); + Document readDocument = await client.ReadDocumentAsync(UriFactory.CreateDocumentUri(database.Id, collection.Id, document.Id), new RequestOptions { PartitionKey = pKey }); + Assert.AreEqual(document.ToString(), readDocument.ToString()); + } - DocumentCollection collTemp2 = await client.ReadDocumentCollectionAsync(UriFactory.CreateDocumentCollectionUri("db1", "coll2")); - Assert.AreEqual(collTemp2, coll2); + //Document Update. + foreach (ResourceResponse obj in documents) + { + PartitionKey pKey = new PartitionKey(new object[] { obj.Resource.GetValue("ZipCode"), obj.Resource.GetPropertyValue("Address") }); + Document document = (await client.ReadDocumentAsync(UriFactory.CreateDocumentUri(database.Id, collection.Id, obj.Resource.Id), new RequestOptions { PartitionKey = pKey })).Resource; + document.SetPropertyValue("Name", document.Id); + Document readDocument = await client.UpsertDocumentAsync(UriFactory.CreateDocumentCollectionUri(database.Id, collection.Id), document);//, new RequestOptions { PartitionKey = pKey }); + Assert.AreEqual(readDocument.GetValue("Name"), document.GetValue("Name")); + } - PartitionKeyDefinition partitionKeyDefinition3 = new PartitionKeyDefinition { Paths = new System.Collections.ObjectModel.Collection(new[] { "/pKey", "/Id", "/Name" }), Kind = PartitionKind.MultiHash }; - DocumentCollection coll3 = await TestCommon.CreateCollectionAsync(client, "/dbs/db1", new DocumentCollection { Id = "coll3", PartitionKey = partitionKeyDefinition3 }); + //Document Delete. + foreach (Document document in documents) + { + PartitionKey pKey = new PartitionKey(new object[] { document.GetValue("ZipCode"), document.GetPropertyValue("Address") }); + Document readDocument = await client.DeleteDocumentAsync(UriFactory.CreateDocumentUri(database.Id, collection.Id, document.Id), new RequestOptions { PartitionKey = pKey }); + try + { + readDocument = await client.ReadDocumentAsync(UriFactory.CreateDocumentUri(database.Id, collection.Id, document.Id), new RequestOptions { PartitionKey = pKey }); + } + catch (DocumentClientException clientException) + { + TestCommon.AssertException(clientException, HttpStatusCode.NotFound); + } + } - DocumentCollection collTemp3 = await client.ReadDocumentCollectionAsync(UriFactory.CreateDocumentCollectionUri("db1", "coll3")); - Assert.AreEqual(collTemp3, coll3); + } + catch (Exception) + { + Assert.Fail(); + } + finally + { + await client.DeleteDatabaseAsync(database); + TestCommon.SetBooleanConfigurationProperty("enableSubPartitioning", false); + } } From ae6c6f96269ed1dd7e421ac65c13c5fbca2f58ea Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Tue, 23 Jun 2020 18:19:26 -0700 Subject: [PATCH 03/36] Document CRUD With MultiHash --- .../Resource/Container/ContainerCore.Items.cs | 52 ++++++++++++++--- .../src/Resource/Container/ContainerCore.cs | 5 +- .../Resource/Container/ContainerInternal.cs | 4 +- .../Resource/Settings/ContainerProperties.cs | 7 ++- .../NameRoutingTests.cs | 58 +++++++++---------- 5 files changed, 83 insertions(+), 43 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 312a907b35..9bd78d534f 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -762,26 +762,33 @@ public override async Task GetPartitionKeyValueFromStreamAsync( stream.CopyTo(memoryStream); } + PartitionKeyDefinition partitionKeyDefinition = await this.GetPartitionKeyDefinitionAsync(); // TODO: Avoid copy IJsonNavigator jsonNavigator = JsonNavigator.Create(memoryStream.ToArray()); IJsonNavigatorNode jsonNavigatorNode = jsonNavigator.GetRootNode(); CosmosObject pathTraversal = CosmosObject.Create(jsonNavigator, jsonNavigatorNode); + List partitionValues = new List(); - string[] tokens = await this.GetPartitionKeyPathTokensAsync(cancellation); - for (int i = 0; i < tokens.Length - 1; i++) + for (int j = 0; j < partitionKeyDefinition.Paths.Count; j++) { - if (!pathTraversal.TryGetValue(tokens[i], out pathTraversal)) + string[] tokens = await this.GetPartitionKeyPathTokensAsync(cancellation, j); + for (int i = 0; i < tokens.Length - 1; i++) + { + if (!pathTraversal.TryGetValue(tokens[i], out pathTraversal)) + { + return PartitionKey.None; + } + } + + if (!pathTraversal.TryGetValue(tokens[tokens.Length - 1], out CosmosElement partitionKeyValue)) { return PartitionKey.None; } - } - if (!pathTraversal.TryGetValue(tokens[tokens.Length - 1], out CosmosElement partitionKeyValue)) - { - return PartitionKey.None; + partitionValues.Add(partitionKeyValue); } - return this.CosmosElementToPartitionKeyObject(partitionKeyValue); + return this.CosmosElementToPartitionKeyObject(CosmosArray.Create(partitionValues)); } finally { @@ -811,6 +818,35 @@ private PartitionKey CosmosElementToPartitionKeyObject(CosmosElement cosmosEleme case CosmosElementType.Null: return PartitionKey.Null; + case CosmosElementType.Array: + CosmosArray array = cosmosElement as CosmosArray; + object[] pKeys = new object[array.ToArray().Length]; + int i = 0; + foreach (CosmosElement ce in array.ToArray()) + { + Object cse; + switch (ce.Type) + { + case CosmosElementType.String: + cse = (ce as CosmosString).Value; + break; + + case CosmosElementType.Number: + cse = Number64.ToDouble((ce as CosmosNumber).Value); + break; + + case CosmosElementType.Boolean: + cse = (ce as CosmosBoolean).Value; + break; + + default: + throw new ArgumentException( + string.Format(CultureInfo.InvariantCulture, RMResources.UnsupportedPartitionKeyComponentValue, ce)); + } + pKeys[i++] = cse; + } + return new PartitionKey(pKeys); + default: throw new ArgumentException( string.Format(CultureInfo.InvariantCulture, RMResources.UnsupportedPartitionKeyComponentValue, cosmosElement)); diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs index 10482bfea0..911dbe1cda 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs @@ -355,8 +355,9 @@ public override async Task GetRIDAsync(CancellationToken cancellationTok /// Used by typed API only. Exceptions are allowed. /// /// + /// /// Returns the partition key path - public override async Task GetPartitionKeyPathTokensAsync(CancellationToken cancellationToken = default(CancellationToken)) + public override async Task GetPartitionKeyPathTokensAsync(CancellationToken cancellationToken = default(CancellationToken), int index = 0) { ContainerProperties containerProperties = await this.GetCachedContainerPropertiesAsync(cancellationToken); if (containerProperties == null) @@ -369,7 +370,7 @@ public override async Task GetRIDAsync(CancellationToken cancellationTok throw new ArgumentOutOfRangeException($"Partition key not defined for container {this.LinkUri.ToString()}"); } - return containerProperties.PartitionKeyPathTokens; + return containerProperties.getPartitionKeyPath(index); } /// diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs index d9b636d2d0..483c47556e 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs @@ -40,7 +40,9 @@ public abstract Task ReplaceThroughputIfExistsAsync( public abstract Task GetCachedContainerPropertiesAsync( CancellationToken cancellationToken = default(CancellationToken)); - public abstract Task GetPartitionKeyPathTokensAsync(CancellationToken cancellationToken = default(CancellationToken)); + public abstract Task GetPartitionKeyPathTokensAsync( + CancellationToken cancellationToken = default(CancellationToken), + int index = 0); public abstract Task GetNonePartitionKeyValueAsync( CancellationToken cancellationToken); diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs index 0d25cc3314..db9096a7ac 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs @@ -519,7 +519,7 @@ internal string[] PartitionKeyPathTokens return this.partitionKeyPathTokens; } - if (this.PartitionKey.Paths.Count > 1) + if (this.PartitionKey.Paths.Count > 1 && this.PartitionKey.Kind != PartitionKind.MultiHash) { throw new NotImplementedException("PartitionKey extraction with composite partition keys not supported."); } @@ -559,5 +559,10 @@ internal void ValidateRequiredProperties() this.indexingPolicyInternal.IncludedPaths.Add(new IncludedPath() { Path = IndexingPolicy.DefaultPath }); } } + + internal string[] getPartitionKeyPath(int index) + { + return this.PartitionKey?.Paths[index].Split(ContainerProperties.partitionKeyTokenDelimeter, StringSplitOptions.RemoveEmptyEntries); + } } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs index bdc7442e96..e02aa8942a 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs @@ -1789,73 +1789,69 @@ public async Task TestScriptCreateOnContainerRecreateFromDifferentPartitionKeyPa [TestMethod] public async Task VerifyDocumentCrudWithMultiHashKind() { - DocumentClient client = TestCommon.CreateClient(true); - Database database = await client.CreateDatabaseAsync(new Database { Id = "mydb" }); + CosmosClient client = TestCommon.CreateCosmosClient(true); + Cosmos.Database database = null; + database = await client.CreateDatabaseAsync("mydb"); try { TestCommon.SetBooleanConfigurationProperty("enableSubPartitioning", true); - DocumentCollection collection = await TestCommon.CreateCollectionAsync(client, - "/dbs/mydb", - new DocumentCollection - { - Id = "mycoll", - PartitionKey = new PartitionKeyDefinition - { - Paths = new Collection { "/ZipCode", "/Address" }, - Kind = PartitionKind.MultiHash, - Version = PartitionKeyDefinitionVersion.V2 - } - }); - + PartitionKeyDefinition pKDefinition = new PartitionKeyDefinition + { + Paths = new Collection { "/ZipCode", "/Address" }, + Kind = PartitionKind.MultiHash, + Version = PartitionKeyDefinitionVersion.V2 + }; + Container container = await database.CreateContainerAsync(containerProperties: new ContainerProperties { Id = "mycoll", PartitionKey = pKDefinition }); + //Document create. - ResourceResponse[] documents = new ResourceResponse[3]; + ItemResponse[] documents = new ItemResponse[3]; Document doc1 = new Document { Id = "document1" }; doc1.SetValue("ZipCode", "500026"); doc1.SetValue("Address", "Secunderabad"); - documents[0] = await client.CreateDocumentAsync("/dbs/mydb/colls/mycoll", doc1); + documents[0] = await container.CreateItemAsync(doc1); doc1 = new Document { Id = "document2" }; doc1.SetValue("ZipCode", "15232"); doc1.SetValue("Address", "Pittsburgh"); - documents[1] = await client.CreateDocumentAsync("/dbs/mydb/colls/mycoll", doc1); + documents[1] = await container.CreateItemAsync(doc1); doc1 = new Document { Id = "document3" }; doc1.SetValue("ZipCode", "11790"); doc1.SetValue("Address", "Stonybrook"); - documents[2] = await client.CreateDocumentAsync("/dbs/mydb/colls/mycoll", doc1); + documents[2] = await container.CreateItemAsync(doc1); Assert.AreEqual(3, documents.Select(document => ((Document)document).SelfLink).Distinct().Count()); //Document Read. foreach (Document document in documents) { - PartitionKey pKey = new PartitionKey(new object[] { document.GetValue("ZipCode"), document.GetPropertyValue("Address") }); - Document readDocument = await client.ReadDocumentAsync(UriFactory.CreateDocumentUri(database.Id, collection.Id, document.Id), new RequestOptions { PartitionKey = pKey }); + Cosmos.PartitionKey pKey = new Cosmos.PartitionKey(new object[] { document.GetValue("ZipCode"), document.GetPropertyValue("Address") }); + Document readDocument = (await container.ReadItemAsync(document.Id, pKey)).Resource; Assert.AreEqual(document.ToString(), readDocument.ToString()); } //Document Update. - foreach (ResourceResponse obj in documents) + foreach (ItemResponse obj in documents) { - PartitionKey pKey = new PartitionKey(new object[] { obj.Resource.GetValue("ZipCode"), obj.Resource.GetPropertyValue("Address") }); - Document document = (await client.ReadDocumentAsync(UriFactory.CreateDocumentUri(database.Id, collection.Id, obj.Resource.Id), new RequestOptions { PartitionKey = pKey })).Resource; + Cosmos.PartitionKey pKey = new Cosmos.PartitionKey(new object[] { obj.Resource.GetValue("ZipCode"), obj.Resource.GetPropertyValue("Address") }); + Document document = (await container.ReadItemAsync(obj.Resource.Id, pKey)).Resource; document.SetPropertyValue("Name", document.Id); - Document readDocument = await client.UpsertDocumentAsync(UriFactory.CreateDocumentCollectionUri(database.Id, collection.Id), document);//, new RequestOptions { PartitionKey = pKey }); + Document readDocument = (await container.ReplaceItemAsync(document, document.Id, pKey)).Resource; Assert.AreEqual(readDocument.GetValue("Name"), document.GetValue("Name")); } //Document Delete. foreach (Document document in documents) { - PartitionKey pKey = new PartitionKey(new object[] { document.GetValue("ZipCode"), document.GetPropertyValue("Address") }); - Document readDocument = await client.DeleteDocumentAsync(UriFactory.CreateDocumentUri(database.Id, collection.Id, document.Id), new RequestOptions { PartitionKey = pKey }); + Cosmos.PartitionKey pKey = new Cosmos.PartitionKey(new object[] { document.GetValue("ZipCode"), document.GetPropertyValue("Address") }); + Document readDocument = (await container.DeleteItemAsync(document.Id, pKey)).Resource; try { - readDocument = await client.ReadDocumentAsync(UriFactory.CreateDocumentUri(database.Id, collection.Id, document.Id), new RequestOptions { PartitionKey = pKey }); + readDocument = await container.ReadItemAsync(document.Id, pKey); } - catch (DocumentClientException clientException) + catch (CosmosException clientException) { - TestCommon.AssertException(clientException, HttpStatusCode.NotFound); + Assert.AreEqual(clientException.StatusCode, HttpStatusCode.NotFound); } } @@ -1866,7 +1862,7 @@ public async Task VerifyDocumentCrudWithMultiHashKind() } finally { - await client.DeleteDatabaseAsync(database); + await database.DeleteAsync(); TestCommon.SetBooleanConfigurationProperty("enableSubPartitioning", false); } From 6df7605f457390c43679ceb15134a4fdd0e33ca7 Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Wed, 24 Jun 2020 15:53:12 -0700 Subject: [PATCH 04/36] Revisions based on code review. --- .../Resource/Container/ContainerCore.Items.cs | 15 +++++++++------ .../src/Resource/Container/ContainerCore.cs | 5 ++--- .../Resource/Container/ContainerInternal.cs | 5 ++--- .../Resource/Settings/ContainerProperties.cs | 19 ++++++++++--------- .../CosmosContainerTests.cs | 2 +- .../CosmosItemUnitTests.cs | 4 ++-- 6 files changed, 26 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 9bd78d534f..0ca47cd26e 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -767,22 +767,24 @@ public override async Task GetPartitionKeyValueFromStreamAsync( IJsonNavigator jsonNavigator = JsonNavigator.Create(memoryStream.ToArray()); IJsonNavigatorNode jsonNavigatorNode = jsonNavigator.GetRootNode(); CosmosObject pathTraversal = CosmosObject.Create(jsonNavigator, jsonNavigatorNode); - List partitionValues = new List(); - for (int j = 0; j < partitionKeyDefinition.Paths.Count; j++) + List partitionValues = new List(); + IReadOnlyList tokenslist = await this.GetPartitionKeyPathTokensAsync(cancellation); + foreach (string[] tokens in tokenslist) { - string[] tokens = await this.GetPartitionKeyPathTokensAsync(cancellation, j); + CosmosElement partitionKeyValue; for (int i = 0; i < tokens.Length - 1; i++) { if (!pathTraversal.TryGetValue(tokens[i], out pathTraversal)) { - return PartitionKey.None; + partitionKeyValue = CosmosNull.Create(); + break; } } - if (!pathTraversal.TryGetValue(tokens[tokens.Length - 1], out CosmosElement partitionKeyValue)) + if (!pathTraversal.TryGetValue(tokens[tokens.Length - 1], out partitionKeyValue)) { - return PartitionKey.None; + partitionKeyValue = CosmosNull.Create(); } partitionValues.Add(partitionKeyValue); @@ -839,6 +841,7 @@ private PartitionKey CosmosElementToPartitionKeyObject(CosmosElement cosmosEleme cse = (ce as CosmosBoolean).Value; break; + case CosmosElementType.Null: default: throw new ArgumentException( string.Format(CultureInfo.InvariantCulture, RMResources.UnsupportedPartitionKeyComponentValue, ce)); diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs index 911dbe1cda..8eae774b4b 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs @@ -355,9 +355,8 @@ public override async Task GetRIDAsync(CancellationToken cancellationTok /// Used by typed API only. Exceptions are allowed. /// /// - /// /// Returns the partition key path - public override async Task GetPartitionKeyPathTokensAsync(CancellationToken cancellationToken = default(CancellationToken), int index = 0) + public override async Task> GetPartitionKeyPathTokensAsync(CancellationToken cancellationToken = default(CancellationToken)) { ContainerProperties containerProperties = await this.GetCachedContainerPropertiesAsync(cancellationToken); if (containerProperties == null) @@ -370,7 +369,7 @@ public override async Task GetRIDAsync(CancellationToken cancellationTok throw new ArgumentOutOfRangeException($"Partition key not defined for container {this.LinkUri.ToString()}"); } - return containerProperties.getPartitionKeyPath(index); + return containerProperties.PartitionKeyPathTokens; } /// diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs index 483c47556e..09bed14358 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs @@ -40,9 +40,8 @@ public abstract Task ReplaceThroughputIfExistsAsync( public abstract Task GetCachedContainerPropertiesAsync( CancellationToken cancellationToken = default(CancellationToken)); - public abstract Task GetPartitionKeyPathTokensAsync( - CancellationToken cancellationToken = default(CancellationToken), - int index = 0); + public abstract Task> GetPartitionKeyPathTokensAsync( + CancellationToken cancellationToken = default(CancellationToken)); public abstract Task GetNonePartitionKeyValueAsync( CancellationToken cancellationToken); diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs index db9096a7ac..842d218e53 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs @@ -5,6 +5,7 @@ namespace Microsoft.Azure.Cosmos { using System; + using System.Collections.Generic; using System.Collections.ObjectModel; using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Routing; @@ -70,7 +71,7 @@ public class ContainerProperties [JsonProperty(PropertyName = Constants.Properties.ConflictResolutionPolicy, NullValueHandling = NullValueHandling.Ignore)] private ConflictResolutionPolicy conflictResolutionInternal; - private string[] partitionKeyPathTokens; + private IList partitionKeyPathTokens; private string id; /// @@ -510,13 +511,13 @@ internal ContainerProperties(string id, PartitionKeyDefinition partitionKeyDefin internal bool HasPartitionKey => this.PartitionKey != null; - internal string[] PartitionKeyPathTokens + internal IReadOnlyList PartitionKeyPathTokens { get { if (this.partitionKeyPathTokens != null) { - return this.partitionKeyPathTokens; + return (IReadOnlyList)this.partitionKeyPathTokens; } if (this.PartitionKey.Paths.Count > 1 && this.PartitionKey.Kind != PartitionKind.MultiHash) @@ -529,8 +530,12 @@ internal string[] PartitionKeyPathTokens throw new ArgumentOutOfRangeException($"Container {this.Id} is not partitioned"); } - this.partitionKeyPathTokens = this.PartitionKeyPath.Split(ContainerProperties.partitionKeyTokenDelimeter, StringSplitOptions.RemoveEmptyEntries); - return this.partitionKeyPathTokens; + this.partitionKeyPathTokens = new List(); + foreach (string s in this.PartitionKey?.Paths) + { + this.partitionKeyPathTokens.Add(s.Split(ContainerProperties.partitionKeyTokenDelimeter, StringSplitOptions.RemoveEmptyEntries)); + } + return (IReadOnlyList)this.partitionKeyPathTokens; } } @@ -560,9 +565,5 @@ internal void ValidateRequiredProperties() } } - internal string[] getPartitionKeyPath(int index) - { - return this.PartitionKey?.Paths[index].Split(ContainerProperties.partitionKeyTokenDelimeter, StringSplitOptions.RemoveEmptyEntries); - } } } \ No newline at end of file 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 ce89ac5309..7813072556 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs @@ -1056,7 +1056,7 @@ private void ValidateCreateContainerResponseContract(ContainerResponse container Assert.IsNotNull(containerSettings.PartitionKeyPath); Assert.IsNotNull(containerSettings.PartitionKeyPathTokens); - Assert.AreEqual(1, containerSettings.PartitionKeyPathTokens.Length); + Assert.AreEqual(1, containerSettings.PartitionKeyPathTokens[0].Length); Assert.AreEqual("id", containerSettings.PartitionKeyPathTokens[0]); ContainerInternal containerCore = containerResponse.Container as ContainerInlineCore; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosItemUnitTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosItemUnitTests.cs index 870e5b995b..2606509d7c 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosItemUnitTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosItemUnitTests.cs @@ -86,7 +86,7 @@ public async Task TestGetPartitionKeyValueFromStreamAsync() ContainerInternal container = containerMock.Object; containerMock.Setup(e => e.GetPartitionKeyPathTokensAsync(It.IsAny())) - .Returns(Task.FromResult(new string[] { "pk" })); + .Returns(Task.FromResult((IReadOnlyList)new List { new string[] { "pk" } })); containerMock.Setup(x => x.GetPartitionKeyValueFromStreamAsync(It.IsAny(), It.IsAny())) .Returns((stream, cancellationToken) => mockContainer.GetPartitionKeyValueFromStreamAsync(stream, cancellationToken)); @@ -393,7 +393,7 @@ public async Task TestNestedPartitionKeyValueFromStreamAsync() ContainerInternal container = containerMock.Object; containerMock.Setup(e => e.GetPartitionKeyPathTokensAsync(It.IsAny())) - .Returns(Task.FromResult(new string[] { "a", "b", "c" })); + .Returns(Task.FromResult((IReadOnlyList) new List{ new string[] { "a", "b", "c" } })); containerMock.Setup(x => x.GetPartitionKeyValueFromStreamAsync(It.IsAny(), It.IsAny())) .Returns((stream, cancellationToken) => mockContainer.GetPartitionKeyValueFromStreamAsync(stream, cancellationToken)); From 98be829343d34917a9d71faeb8e3ba040401b19b Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Thu, 25 Jun 2020 08:55:22 -0700 Subject: [PATCH 05/36] Resolving comments, adding a negative test. --- .../Resource/Container/ContainerCore.Items.cs | 17 +++++++++++++---- .../NameRoutingTests.cs | 11 +++++++++++ 2 files changed, 24 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 a7ee1b5818..e76e874e2b 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -626,6 +626,11 @@ private async Task ExtractPartitionKeyAndProcessItemStreamAsync // User specified PK value, no need to extract it if (partitionKey.HasValue) { + PartitionKeyDefinition pKeyDefinition = await this.GetPartitionKeyDefinitionAsync(); + if (((PartitionKey)partitionKey).InternalKey.Components.Count != pKeyDefinition.Paths.Count) + { + throw new ArgumentException(RMResources.PartitionKeyMismatch); + } return await this.ProcessItemStreamAsync( partitionKey, itemId, @@ -728,7 +733,6 @@ public override async Task GetPartitionKeyValueFromStreamAsync( stream.CopyTo(memoryStream); } - PartitionKeyDefinition partitionKeyDefinition = await this.GetPartitionKeyDefinitionAsync(); // TODO: Avoid copy IJsonNavigator jsonNavigator = JsonNavigator.Create(memoryStream.ToArray()); IJsonNavigatorNode jsonNavigatorNode = jsonNavigator.GetRootNode(); @@ -743,16 +747,18 @@ public override async Task GetPartitionKeyValueFromStreamAsync( { if (!pathTraversal.TryGetValue(tokens[i], out pathTraversal)) { - partitionKeyValue = CosmosNull.Create(); - break; + if (tokenslist.Count == 1) return PartitionKey.None; + throw new ArgumentException(RMResources.PartitionKeyMismatch); } } if (!pathTraversal.TryGetValue(tokens[tokens.Length - 1], out partitionKeyValue)) { - partitionKeyValue = CosmosNull.Create(); + if (tokenslist.Count == 1) return PartitionKey.None; + throw new ArgumentException(RMResources.PartitionKeyMismatch); } + if (partitionKeyValue == null) partitionKeyValue = CosmosNull.Create(); partitionValues.Add(partitionKeyValue); } @@ -808,6 +814,9 @@ private PartitionKey CosmosElementToPartitionKeyObject(CosmosElement cosmosEleme break; case CosmosElementType.Null: + cse = Undefined.Value; + break; + default: throw new ArgumentException( string.Format(CultureInfo.InvariantCulture, RMResources.UnsupportedPartitionKeyComponentValue, ce)); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs index e02aa8942a..bdb43ffc14 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs @@ -1822,6 +1822,17 @@ public async Task VerifyDocumentCrudWithMultiHashKind() Assert.AreEqual(3, documents.Select(document => ((Document)document).SelfLink).Distinct().Count()); + try + { + doc1 = new Document { Id = "doc1" }; + doc1.SetValue("Zipcode", "11790"); + Cosmos.PartitionKey pKeyErr = new Cosmos.PartitionKey(new Object[] { doc1.GetPropertyValue("ZipCode") }); + await container.CreateItemAsync(doc1, pKeyErr); + } + catch (ArgumentException ar) + { + Assert.AreEqual(ar.Message, RMResources.PartitionKeyMismatch); + } //Document Read. foreach (Document document in documents) { From 1c87f5c3665b7b1a54b998df0bbfffe433f11697 Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Thu, 25 Jun 2020 21:49:40 -0700 Subject: [PATCH 06/36] Fixed suggested in code review. --- Microsoft.Azure.Cosmos/src/PartitionKey.cs | 6 +- .../src/PartitionKeyValueList.cs | 66 ++++++++++++++++++ .../Resource/Container/ContainerCore.Items.cs | 68 +++++++------------ 3 files changed, 95 insertions(+), 45 deletions(-) create mode 100644 Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs diff --git a/Microsoft.Azure.Cosmos/src/PartitionKey.cs b/Microsoft.Azure.Cosmos/src/PartitionKey.cs index 4280d8b57a..ec5a153382 100644 --- a/Microsoft.Azure.Cosmos/src/PartitionKey.cs +++ b/Microsoft.Azure.Cosmos/src/PartitionKey.cs @@ -4,6 +4,7 @@ namespace Microsoft.Azure.Cosmos { using System; + using System.Linq; using Microsoft.Azure.Documents.Routing; /// @@ -99,9 +100,10 @@ internal PartitionKey(object value) /// Creates a new partition key value. /// /// The value to use as partition key. - internal PartitionKey(object[] value) + internal PartitionKey(PartitionKeyValueList value) { - this.InternalKey = new Documents.PartitionKey(value).InternalKey; + object[] valueArray = value.partitionKeyObjects.ToArray(); + this.InternalKey = new Documents.PartitionKey(valueArray).InternalKey; this.IsNone = false; } diff --git a/Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs b/Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs new file mode 100644 index 0000000000..50c584ef61 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs @@ -0,0 +1,66 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ +namespace Microsoft.Azure.Cosmos +{ + using System; + using System.Collections; + using System.Collections.Generic; + using Microsoft.Azure.Documents; + using Microsoft.Azure.Documents.Routing; + + /// + /// Represents a partition key value list in the Azure Cosmos DB service. + /// + public class PartitionKeyValueList + { + private IList partitionKeyValues; + + /// + /// Creates a new partition key value list. + /// + public PartitionKeyValueList() + { + this.partitionKeyValues = new List(); + } + + /// + /// Sets a partition key value of type string. + /// + /// The value of type string to be used as partitionKey. + public void Add(string val) + { + if (val == null) + { + this.Add(); + return; + } + this.partitionKeyValues.Add(val); + } + + /// + /// Sets a partition key value of type double. + /// + /// The value of type double to be used as partitionKey. + public void Add(double val) + { + this.partitionKeyValues.Add(val); + } + + /// + /// Sets a partition key value of type bool. + /// + /// The value of type bool to be used as partitionKey. + public void Add(bool val) + { + this.partitionKeyValues.Add(val); + } + + private void Add() + { + this.partitionKeyValues.Add(Undefined.Value); + } + + internal IReadOnlyCollection partitionKeyObjects => (IReadOnlyCollection)this.partitionKeyValues; + } +} \ 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 e76e874e2b..da7b8e897a 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -738,8 +738,9 @@ public override async Task GetPartitionKeyValueFromStreamAsync( IJsonNavigatorNode jsonNavigatorNode = jsonNavigator.GetRootNode(); CosmosObject pathTraversal = CosmosObject.Create(jsonNavigator, jsonNavigatorNode); - List partitionValues = new List(); IReadOnlyList tokenslist = await this.GetPartitionKeyPathTokensAsync(cancellation); + PartitionKeyDefinition partitionKeyDefinition = await this.GetPartitionKeyDefinitionAsync(); + PartitionKeyValueList partitionKeyValueList = new PartitionKeyValueList(); foreach (string[] tokens in tokenslist) { CosmosElement partitionKeyValue; @@ -748,21 +749,24 @@ public override async Task GetPartitionKeyValueFromStreamAsync( if (!pathTraversal.TryGetValue(tokens[i], out pathTraversal)) { if (tokenslist.Count == 1) return PartitionKey.None; - throw new ArgumentException(RMResources.PartitionKeyMismatch); + break; } } if (!pathTraversal.TryGetValue(tokens[tokens.Length - 1], out partitionKeyValue)) { if (tokenslist.Count == 1) return PartitionKey.None; - throw new ArgumentException(RMResources.PartitionKeyMismatch); } - if (partitionKeyValue == null) partitionKeyValue = CosmosNull.Create(); - partitionValues.Add(partitionKeyValue); - } + if (partitionKeyValue == null) + { + partitionKeyValue = CosmosNull.Create(); + } - return this.CosmosElementToPartitionKeyObject(CosmosArray.Create(partitionValues)); + this.CosmosElementToPartitionKeyObject(partitionKeyValue, partitionKeyValueList, partitionKeyDefinition); + } + + return new PartitionKey(partitionKeyValueList); } finally { @@ -771,59 +775,37 @@ public override async Task GetPartitionKeyValueFromStreamAsync( } } - private PartitionKey CosmosElementToPartitionKeyObject(CosmosElement cosmosElement) + private void CosmosElementToPartitionKeyObject(CosmosElement cosmosElement, PartitionKeyValueList partitionKeyValueList, PartitionKeyDefinition partitionKeyDefinition) { // TODO: Leverage original serialization and avoid re-serialization (bug) switch (cosmosElement.Type) { case CosmosElementType.String: CosmosString cosmosString = cosmosElement as CosmosString; - return new PartitionKey(cosmosString.Value); + partitionKeyValueList.Add(cosmosString.Value); + break; case CosmosElementType.Number: CosmosNumber cosmosNumber = cosmosElement as CosmosNumber; double value = Number64.ToDouble(cosmosNumber.Value); - return new PartitionKey(value); + partitionKeyValueList.Add(value); + break; case CosmosElementType.Boolean: CosmosBoolean cosmosBool = cosmosElement as CosmosBoolean; - return new PartitionKey(cosmosBool.Value); + partitionKeyValueList.Add(cosmosBool.Value); + break; case CosmosElementType.Null: - return PartitionKey.Null; - - case CosmosElementType.Array: - CosmosArray array = cosmosElement as CosmosArray; - object[] pKeys = new object[array.ToArray().Length]; - int i = 0; - foreach (CosmosElement ce in array.ToArray()) + if (partitionKeyDefinition == null) { - Object cse; - switch (ce.Type) - { - case CosmosElementType.String: - cse = (ce as CosmosString).Value; - break; - - case CosmosElementType.Number: - cse = Number64.ToDouble((ce as CosmosNumber).Value); - break; - - case CosmosElementType.Boolean: - cse = (ce as CosmosBoolean).Value; - break; - - case CosmosElementType.Null: - cse = Undefined.Value; - break; - - default: - throw new ArgumentException( - string.Format(CultureInfo.InvariantCulture, RMResources.UnsupportedPartitionKeyComponentValue, ce)); - } - pKeys[i++] = cse; + throw new ArgumentNullException($"{nameof(partitionKeyDefinition)}"); + } + else + { + partitionKeyValueList.Add(null); // This is translated to PartitionKey.Undefined } - return new PartitionKey(pKeys); + break; default: throw new ArgumentException( From 64af5a62e3a72da0c0ce20225a9b3fecadcd4356 Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Fri, 26 Jun 2020 12:21:13 -0700 Subject: [PATCH 07/36] Tidying up the comments and Error messages. --- Microsoft.Azure.Cosmos/src/PartitionKey.cs | 2 +- Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs | 2 +- .../src/Resource/Container/ContainerCore.Items.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/PartitionKey.cs b/Microsoft.Azure.Cosmos/src/PartitionKey.cs index ec5a153382..000734275e 100644 --- a/Microsoft.Azure.Cosmos/src/PartitionKey.cs +++ b/Microsoft.Azure.Cosmos/src/PartitionKey.cs @@ -99,7 +99,7 @@ internal PartitionKey(object value) /// /// Creates a new partition key value. /// - /// The value to use as partition key. + /// An object of Type PartitionKeyValueList which supports multiple partition key paths. internal PartitionKey(PartitionKeyValueList value) { object[] valueArray = value.partitionKeyObjects.ToArray(); diff --git a/Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs b/Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs index 50c584ef61..9f6bcee871 100644 --- a/Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs +++ b/Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs @@ -17,7 +17,7 @@ public class PartitionKeyValueList private IList partitionKeyValues; /// - /// Creates a new partition key value list. + /// Creates a new partition key value list object. /// public PartitionKeyValueList() { diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index da7b8e897a..a3800ff367 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -629,7 +629,7 @@ private async Task ExtractPartitionKeyAndProcessItemStreamAsync PartitionKeyDefinition pKeyDefinition = await this.GetPartitionKeyDefinitionAsync(); if (((PartitionKey)partitionKey).InternalKey.Components.Count != pKeyDefinition.Paths.Count) { - throw new ArgumentException(RMResources.PartitionKeyMismatch); + throw new ArgumentException(RMResources.MissingPartitionKeyValue); } return await this.ProcessItemStreamAsync( partitionKey, From 80c0d59836ee1b34ff9c2aaff51ca5508e5ca56a Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Thu, 2 Jul 2020 16:36:19 -0700 Subject: [PATCH 08/36] Implementing suggested changes in PR. --- Microsoft.Azure.Cosmos/src/PartitionKey.cs | 11 +++++++-- .../src/PartitionKeyValueList.cs | 23 +++++++++++-------- .../Resource/Container/ContainerCore.Items.cs | 12 ++++++---- .../Resource/Settings/ContainerProperties.cs | 12 ++++++---- .../NameRoutingTests.cs | 3 +-- 5 files changed, 38 insertions(+), 23 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/PartitionKey.cs b/Microsoft.Azure.Cosmos/src/PartitionKey.cs index 000734275e..18cc4e399b 100644 --- a/Microsoft.Azure.Cosmos/src/PartitionKey.cs +++ b/Microsoft.Azure.Cosmos/src/PartitionKey.cs @@ -102,8 +102,15 @@ internal PartitionKey(object value) /// An object of Type PartitionKeyValueList which supports multiple partition key paths. internal PartitionKey(PartitionKeyValueList value) { - object[] valueArray = value.partitionKeyObjects.ToArray(); - this.InternalKey = new Documents.PartitionKey(valueArray).InternalKey; + if (value == null) + { + this.InternalKey = PartitionKey.NullPartitionKeyInternal; + } + else + { + object[] valueArray = value.partitionKeyObjects.ToArray(); + this.InternalKey = new Documents.PartitionKey(valueArray).InternalKey; + } this.IsNone = false; } diff --git a/Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs b/Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs index 9f6bcee871..e2e65edd93 100644 --- a/Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs +++ b/Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs @@ -12,9 +12,9 @@ namespace Microsoft.Azure.Cosmos /// /// Represents a partition key value list in the Azure Cosmos DB service. /// - public class PartitionKeyValueList + public sealed class PartitionKeyValueList { - private IList partitionKeyValues; + private readonly IList partitionKeyValues; /// /// Creates a new partition key value list object. @@ -25,21 +25,23 @@ public PartitionKeyValueList() } /// - /// Sets a partition key value of type string. + /// Adds a partition key value of type string to the list. /// /// The value of type string to be used as partitionKey. public void Add(string val) { if (val == null) { - this.Add(); - return; + this.AddUndefined(); + } + else + { + this.partitionKeyValues.Add(val); } - this.partitionKeyValues.Add(val); } /// - /// Sets a partition key value of type double. + /// Adds a partition key value of type double to the list. /// /// The value of type double to be used as partitionKey. public void Add(double val) @@ -48,7 +50,7 @@ public void Add(double val) } /// - /// Sets a partition key value of type bool. + /// Adds a partition key value of type bool to the list. /// /// The value of type bool to be used as partitionKey. public void Add(bool val) @@ -56,7 +58,10 @@ public void Add(bool val) this.partitionKeyValues.Add(val); } - private void Add() + /// + /// Adds an Undefined partition key to the list. + /// + public void AddUndefined() { this.partitionKeyValues.Add(Undefined.Value); } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index a3800ff367..157f801275 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -631,6 +631,7 @@ private async Task ExtractPartitionKeyAndProcessItemStreamAsync { throw new ArgumentException(RMResources.MissingPartitionKeyValue); } + return await this.ProcessItemStreamAsync( partitionKey, itemId, @@ -748,7 +749,10 @@ public override async Task GetPartitionKeyValueFromStreamAsync( { if (!pathTraversal.TryGetValue(tokens[i], out pathTraversal)) { - if (tokenslist.Count == 1) return PartitionKey.None; + if (tokenslist.Count == 1) + { + return PartitionKey.None; + } break; } } @@ -801,10 +805,8 @@ private void CosmosElementToPartitionKeyObject(CosmosElement cosmosElement, Part { throw new ArgumentNullException($"{nameof(partitionKeyDefinition)}"); } - else - { - partitionKeyValueList.Add(null); // This is translated to PartitionKey.Undefined - } + + partitionKeyValueList.AddUndefined(); break; default: diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs index 842d218e53..7b4c34d867 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs @@ -71,7 +71,7 @@ public class ContainerProperties [JsonProperty(PropertyName = Constants.Properties.ConflictResolutionPolicy, NullValueHandling = NullValueHandling.Ignore)] private ConflictResolutionPolicy conflictResolutionInternal; - private IList partitionKeyPathTokens; + private IReadOnlyList partitionKeyPathTokens; private string id; /// @@ -517,7 +517,7 @@ internal IReadOnlyList PartitionKeyPathTokens { if (this.partitionKeyPathTokens != null) { - return (IReadOnlyList)this.partitionKeyPathTokens; + return this.partitionKeyPathTokens; } if (this.PartitionKey.Paths.Count > 1 && this.PartitionKey.Kind != PartitionKind.MultiHash) @@ -530,12 +530,14 @@ internal IReadOnlyList PartitionKeyPathTokens throw new ArgumentOutOfRangeException($"Container {this.Id} is not partitioned"); } - this.partitionKeyPathTokens = new List(); + Collection partitionKeyPathTokensList = new Collection(); foreach (string s in this.PartitionKey?.Paths) { - this.partitionKeyPathTokens.Add(s.Split(ContainerProperties.partitionKeyTokenDelimeter, StringSplitOptions.RemoveEmptyEntries)); + partitionKeyPathTokensList.Add(s.Split(ContainerProperties.partitionKeyTokenDelimeter, StringSplitOptions.RemoveEmptyEntries)); } - return (IReadOnlyList)this.partitionKeyPathTokens; + + this.partitionKeyPathTokens = partitionKeyPathTokensList; + return this.partitionKeyPathTokens; } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs index bdb43ffc14..18aabedcc5 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs @@ -1786,6 +1786,7 @@ public async Task TestScriptCreateOnContainerRecreateFromDifferentPartitionKeyPa await this.TestScriptCreateOnContainerRecreateFromDifferentPartitionKeyPath(TestCommon.CreateCosmosClient(true)); } + [Ignore] //Ignoring this test until EnableSubpartitioning is set to true in BE. [TestMethod] public async Task VerifyDocumentCrudWithMultiHashKind() { @@ -1794,7 +1795,6 @@ public async Task VerifyDocumentCrudWithMultiHashKind() database = await client.CreateDatabaseAsync("mydb"); try { - TestCommon.SetBooleanConfigurationProperty("enableSubPartitioning", true); PartitionKeyDefinition pKDefinition = new PartitionKeyDefinition { Paths = new Collection { "/ZipCode", "/Address" }, @@ -1874,7 +1874,6 @@ public async Task VerifyDocumentCrudWithMultiHashKind() finally { await database.DeleteAsync(); - TestCommon.SetBooleanConfigurationProperty("enableSubPartitioning", false); } } From 726eb06257fe2291bef68ead9ae0d3ce97573d04 Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Tue, 7 Jul 2020 12:32:36 -0700 Subject: [PATCH 09/36] PartitionKey Null/None --- Microsoft.Azure.Cosmos/src/PartitionKey.cs | 4 ++-- .../src/PartitionKeyValueList.cs | 14 +++++++++----- .../src/Resource/Container/ContainerCore.Items.cs | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/PartitionKey.cs b/Microsoft.Azure.Cosmos/src/PartitionKey.cs index 18cc4e399b..b804f80f86 100644 --- a/Microsoft.Azure.Cosmos/src/PartitionKey.cs +++ b/Microsoft.Azure.Cosmos/src/PartitionKey.cs @@ -104,11 +104,11 @@ internal PartitionKey(PartitionKeyValueList value) { if (value == null) { - this.InternalKey = PartitionKey.NullPartitionKeyInternal; + this.InternalKey = PartitionKey.None.InternalKey; } else { - object[] valueArray = value.partitionKeyObjects.ToArray(); + object[] valueArray = value.partitionKeyValues.ToArray(); this.InternalKey = new Documents.PartitionKey(valueArray).InternalKey; } this.IsNone = false; diff --git a/Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs b/Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs index e2e65edd93..2e7c327e2b 100644 --- a/Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs +++ b/Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs @@ -14,7 +14,7 @@ namespace Microsoft.Azure.Cosmos /// public sealed class PartitionKeyValueList { - private readonly IList partitionKeyValues; + internal readonly ICollection partitionKeyValues; /// /// Creates a new partition key value list object. @@ -32,7 +32,7 @@ public void Add(string val) { if (val == null) { - this.AddUndefined(); + this.AddNullValue(); } else { @@ -59,13 +59,17 @@ public void Add(bool val) } /// - /// Adds an Undefined partition key to the list. + /// Adds a partition key value which is null /// - public void AddUndefined() + public void AddNullValue() + { + this.partitionKeyValues.Add(null); + } + + internal void AddUndefinedValue() { this.partitionKeyValues.Add(Undefined.Value); } - internal IReadOnlyCollection partitionKeyObjects => (IReadOnlyCollection)this.partitionKeyValues; } } \ 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 157f801275..2f712cfbb0 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -806,7 +806,7 @@ private void CosmosElementToPartitionKeyObject(CosmosElement cosmosElement, Part throw new ArgumentNullException($"{nameof(partitionKeyDefinition)}"); } - partitionKeyValueList.AddUndefined(); + partitionKeyValueList.AddNullValue(); break; default: From 8b708716d75415b75faeca5fed290d115506107c Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Tue, 7 Jul 2020 12:45:06 -0700 Subject: [PATCH 10/36] Correction to undefined --- .../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 2f712cfbb0..5827bad099 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -806,7 +806,7 @@ private void CosmosElementToPartitionKeyObject(CosmosElement cosmosElement, Part throw new ArgumentNullException($"{nameof(partitionKeyDefinition)}"); } - partitionKeyValueList.AddNullValue(); + partitionKeyValueList.AddUndefinedValue(); break; default: From 2dc615840faf0c028af862dd11e1b1da65a251be Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Thu, 9 Jul 2020 23:02:17 -0700 Subject: [PATCH 11/36] Changes suggested in reviews. --- Microsoft.Azure.Cosmos/src/PartitionKey.cs | 22 ++- .../src/PartitionKeyValueList.cs | 10 +- .../Resource/Container/ContainerCore.Items.cs | 125 ++++++++++-------- .../CosmosItemUnitTests.cs | 110 +++++++++++++++ 4 files changed, 208 insertions(+), 59 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/PartitionKey.cs b/Microsoft.Azure.Cosmos/src/PartitionKey.cs index b804f80f86..6d038afaf7 100644 --- a/Microsoft.Azure.Cosmos/src/PartitionKey.cs +++ b/Microsoft.Azure.Cosmos/src/PartitionKey.cs @@ -5,6 +5,7 @@ namespace Microsoft.Azure.Cosmos { using System; using System.Linq; + using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Routing; /// @@ -102,16 +103,31 @@ internal PartitionKey(object value) /// An object of Type PartitionKeyValueList which supports multiple partition key paths. internal PartitionKey(PartitionKeyValueList value) { - if (value == null) + if (value == null + || value.partitionKeyValues.Count == 0 + || (value.partitionKeyValues.Count == 1 && object.ReferenceEquals(value.partitionKeyValues[0], PartitionKeyValueList.NoneType))) { this.InternalKey = PartitionKey.None.InternalKey; + this.IsNone = true; } else { - object[] valueArray = value.partitionKeyValues.ToArray(); + object[] valueArray = new object[value.partitionKeyValues.Count]; + int i = 0; + foreach (object val in value.partitionKeyValues) + { + if (object.ReferenceEquals(PartitionKeyValueList.NoneType, val)) + { + valueArray[i++] = Undefined.Value; + } + else + { + valueArray[i++] = val; + } + } this.InternalKey = new Documents.PartitionKey(valueArray).InternalKey; + this.IsNone = false; } - this.IsNone = false; } /// diff --git a/Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs b/Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs index 2e7c327e2b..5045c063fc 100644 --- a/Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs +++ b/Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs @@ -14,7 +14,9 @@ namespace Microsoft.Azure.Cosmos /// public sealed class PartitionKeyValueList { - internal readonly ICollection partitionKeyValues; + internal readonly IList partitionKeyValues; + + internal static readonly object NoneType = new object(); /// /// Creates a new partition key value list object. @@ -66,10 +68,14 @@ public void AddNullValue() this.partitionKeyValues.Add(null); } - internal void AddUndefinedValue() + internal void AddUndefined() { this.partitionKeyValues.Add(Undefined.Value); } + internal void AddNoneType() + { + this.partitionKeyValues.Add(NoneType); + } } } \ 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 5827bad099..ad403a8991 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -714,7 +714,16 @@ private async Task ProcessItemStreamAsync( return responseMessage; } - + /* + * Use of PartitionKey.None ==> PartitionKey.None is a placeholder used here + * to later discern whether to set the value to either Undefined Key or Empty Key. + * Refer RequestInvokerHandler.SendAsync() method. + * + * The empty key is only used for Non-partitioned Collections. Every other case we + * set it as a Undefined Key. + * + * + */ public override async Task GetPartitionKeyValueFromStreamAsync( Stream stream, CancellationToken cancellation = default(CancellationToken)) @@ -740,37 +749,16 @@ public override async Task GetPartitionKeyValueFromStreamAsync( CosmosObject pathTraversal = CosmosObject.Create(jsonNavigator, jsonNavigatorNode); IReadOnlyList tokenslist = await this.GetPartitionKeyPathTokensAsync(cancellation); - PartitionKeyDefinition partitionKeyDefinition = await this.GetPartitionKeyDefinitionAsync(); - PartitionKeyValueList partitionKeyValueList = new PartitionKeyValueList(); + CosmosElement[] cosmosElementArray = new CosmosElement[tokenslist.Count]; + + int index = 0; foreach (string[] tokens in tokenslist) { - CosmosElement partitionKeyValue; - for (int i = 0; i < tokens.Length - 1; i++) - { - if (!pathTraversal.TryGetValue(tokens[i], out pathTraversal)) - { - if (tokenslist.Count == 1) - { - return PartitionKey.None; - } - break; - } - } - - if (!pathTraversal.TryGetValue(tokens[tokens.Length - 1], out partitionKeyValue)) - { - if (tokenslist.Count == 1) return PartitionKey.None; - } - - if (partitionKeyValue == null) - { - partitionKeyValue = CosmosNull.Create(); - } - - this.CosmosElementToPartitionKeyObject(partitionKeyValue, partitionKeyValueList, partitionKeyDefinition); + CosmosElement partitionKeyValue = this.ParseTokenListForElement(pathTraversal, tokens); + cosmosElementArray[index++] = partitionKeyValue; } - - return new PartitionKey(partitionKeyValueList); + + return this.CosmosElementToPartitionKeyObject(cosmosElementArray); } finally { @@ -779,40 +767,69 @@ public override async Task GetPartitionKeyValueFromStreamAsync( } } - private void CosmosElementToPartitionKeyObject(CosmosElement cosmosElement, PartitionKeyValueList partitionKeyValueList, PartitionKeyDefinition partitionKeyDefinition) + private CosmosElement ParseTokenListForElement(CosmosObject pathTraversal, string[] tokens) { - // TODO: Leverage original serialization and avoid re-serialization (bug) - switch (cosmosElement.Type) + CosmosElement partitionKeyValue; + + for (int i = 0; i < tokens.Length - 1; i++) { - case CosmosElementType.String: - CosmosString cosmosString = cosmosElement as CosmosString; - partitionKeyValueList.Add(cosmosString.Value); - break; + if (!pathTraversal.TryGetValue(tokens[i], out pathTraversal)) + { + return null; + } + } - case CosmosElementType.Number: - CosmosNumber cosmosNumber = cosmosElement as CosmosNumber; - double value = Number64.ToDouble(cosmosNumber.Value); - partitionKeyValueList.Add(value); - break; + if (!pathTraversal.TryGetValue(tokens[tokens.Length - 1], out partitionKeyValue)) + { + return null; + } - case CosmosElementType.Boolean: - CosmosBoolean cosmosBool = cosmosElement as CosmosBoolean; - partitionKeyValueList.Add(cosmosBool.Value); - break; + return partitionKeyValue; + } - case CosmosElementType.Null: - if (partitionKeyDefinition == null) + private PartitionKey CosmosElementToPartitionKeyObject(CosmosElement[] cosmosElementArray) + { + PartitionKeyValueList partitionKeyValueList = new PartitionKeyValueList(); + + foreach (CosmosElement cosmosElement in cosmosElementArray) + { + if (cosmosElement == null) + { + partitionKeyValueList.AddNoneType(); + } + else + { + // TODO: Leverage original serialization and avoid re-serialization (bug) + switch (cosmosElement.Type) { - throw new ArgumentNullException($"{nameof(partitionKeyDefinition)}"); - } + case CosmosElementType.String: + CosmosString cosmosString = cosmosElement as CosmosString; + partitionKeyValueList.Add(cosmosString.Value); + break; - partitionKeyValueList.AddUndefinedValue(); - break; + case CosmosElementType.Number: + CosmosNumber cosmosNumber = cosmosElement as CosmosNumber; + double value = Number64.ToDouble(cosmosNumber.Value); + partitionKeyValueList.Add(value); + break; - default: - throw new ArgumentException( - string.Format(CultureInfo.InvariantCulture, RMResources.UnsupportedPartitionKeyComponentValue, cosmosElement)); + case CosmosElementType.Boolean: + CosmosBoolean cosmosBool = cosmosElement as CosmosBoolean; + partitionKeyValueList.Add(cosmosBool.Value); + break; + + case CosmosElementType.Null: + partitionKeyValueList.AddNullValue(); + break; + + default: + throw new ArgumentException( + string.Format(CultureInfo.InvariantCulture, RMResources.UnsupportedPartitionKeyComponentValue, cosmosElement)); + } + } } + + return new PartitionKey(partitionKeyValueList); } private Uri GetResourceUri(RequestOptions requestOptions, OperationType operationType, string itemId) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosItemUnitTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosItemUnitTests.cs index b6fed2c7ea..68fede732b 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosItemUnitTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosItemUnitTests.cs @@ -10,6 +10,7 @@ namespace Microsoft.Azure.Cosmos.Tests using System.Net; using System.Threading; using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.Json.Interop; using Microsoft.Azure.Cosmos.Query; using Microsoft.Azure.Documents; using Microsoft.VisualBasic; @@ -463,6 +464,115 @@ public async Task TestNestedPartitionKeyValueFromStreamAsync() } } + [Ignore] + [TestMethod] + public async Task TestMultipleNestedPartitionKeyValueFromStreamAsync() + { + ContainerInternal mockContainer = (ContainerInternal)MockCosmosUtil.CreateMockCosmosClient().GetContainer("TestDb", "Test"); + + Mock containerMock = new Mock() { CallBase = true }; + ContainerInternal container = containerMock.Object; + + containerMock.Setup(e => e.GetPartitionKeyPathTokensAsync(It.IsAny())) + .Returns(Task.FromResult((IReadOnlyList)new List { new string[] { "a", "b", "c" }, new string[] { "a","e","f" } })); + containerMock.Setup(x => x.GetPartitionKeyValueFromStreamAsync(It.IsAny(), It.IsAny())) + .Returns((stream, cancellationToken) => mockContainer.GetPartitionKeyValueFromStreamAsync(stream, cancellationToken)); + + List validNestedItems = new List + { + ( + new // a/b/c (Specify only one partition key) + { + id = Guid.NewGuid().ToString(), + a = new + { + b = new + { + c = 10, + } + } + }, + "[10.0,{}]" + ), + ( + new // + { + id = Guid.NewGuid().ToString(), + a = new + { + b = new + { + c = 10, + }, + e = new + { + f = 15, + } + } + }, + "[10.0,15.0]" + ), + ( + new + { + id = Guid.NewGuid().ToString(), + a = new + { + b = new + { + c = 10, + }, + e = new + { + f = default(string), //null + } + } + }, + "[10.0,null]" + ), + ( + new + { + id = Guid.NewGuid().ToString(), + a = new + { + e = new + { + f = 10, + } + } + }, + "[{},10.0]" + ), + ( + new + { + id = Guid.NewGuid().ToString(), + a = new + { + e = 10, + b = new + { + k = 10, + } + } + + }, + "[{},{}]" + ) + }; + + foreach (dynamic poco in validNestedItems) + { + Cosmos.PartitionKey pk = await container.GetPartitionKeyValueFromStreamAsync( + MockCosmosUtil.Serializer.ToStream(poco.Item1), + default(CancellationToken)); + string abc = pk.InternalKey.ToJsonString(); + Assert.AreEqual(abc,poco.Item2); + } + + } + private (ContainerInternal, Mock) CreateMockBulkCosmosClientContext() { CosmosClientContext context = MockCosmosUtil.CreateMockCosmosClient( From a106adffeb1c3d438c46c1d8ab4a6ac09b96a9ca Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Thu, 9 Jul 2020 23:40:42 -0700 Subject: [PATCH 12/36] Tidying up comments. --- Microsoft.Azure.Cosmos/src/PartitionKey.cs | 11 +++++++++++ .../src/Resource/Container/ContainerCore.Items.cs | 11 +---------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/PartitionKey.cs b/Microsoft.Azure.Cosmos/src/PartitionKey.cs index 6d038afaf7..78503eeb20 100644 --- a/Microsoft.Azure.Cosmos/src/PartitionKey.cs +++ b/Microsoft.Azure.Cosmos/src/PartitionKey.cs @@ -103,6 +103,17 @@ internal PartitionKey(object value) /// An object of Type PartitionKeyValueList which supports multiple partition key paths. internal PartitionKey(PartitionKeyValueList value) { + /* + * Why these checks? + * These changes are being added in the PR for SDK to support multiple paths in a partition key. + * + * Currently, when a resource does not specify a value for the PartitionKey, + * we assign a temporary value `PartitionKey.None` and later discern whether + * it is a PartitionKey.Undefined or PartitionKey.Empty based on the Collection Type. + * + * For collections with multiple path keys, absence of a partition key values is + * always treated as a PartitionKey.Undefined. + */ if (value == null || value.partitionKeyValues.Count == 0 || (value.partitionKeyValues.Count == 1 && object.ReferenceEquals(value.partitionKeyValues[0], PartitionKeyValueList.NoneType))) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index ad403a8991..db6b82127f 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -714,16 +714,7 @@ private async Task ProcessItemStreamAsync( return responseMessage; } - /* - * Use of PartitionKey.None ==> PartitionKey.None is a placeholder used here - * to later discern whether to set the value to either Undefined Key or Empty Key. - * Refer RequestInvokerHandler.SendAsync() method. - * - * The empty key is only used for Non-partitioned Collections. Every other case we - * set it as a Undefined Key. - * - * - */ + public override async Task GetPartitionKeyValueFromStreamAsync( Stream stream, CancellationToken cancellation = default(CancellationToken)) From 186518fc5302ecb9768a9b442a137b5b2c0fd82c Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Wed, 15 Jul 2020 00:43:20 -0700 Subject: [PATCH 13/36] Correcting the test case. And implementing review suggestions. --- Microsoft.Azure.Cosmos/src/PartitionKey.cs | 21 ++++----- .../src/PartitionKeyValueList.cs | 26 +++++------ .../CosmosItemUnitTests.cs | 45 ++++++++++++------- 3 files changed, 50 insertions(+), 42 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/PartitionKey.cs b/Microsoft.Azure.Cosmos/src/PartitionKey.cs index 78503eeb20..b56e928f4f 100644 --- a/Microsoft.Azure.Cosmos/src/PartitionKey.cs +++ b/Microsoft.Azure.Cosmos/src/PartitionKey.cs @@ -100,34 +100,35 @@ internal PartitionKey(object value) /// /// Creates a new partition key value. /// - /// An object of Type PartitionKeyValueList which supports multiple partition key paths. - internal PartitionKey(PartitionKeyValueList value) + /// An object of Type PartitionKeyValueList which supports multiple partition key paths. + public PartitionKey(PartitionKeyValueList valueList) { /* * Why these checks? - * These changes are being added in the PR for SDK to support multiple paths in a partition key. + * These changes are being added for SDK to support multiple paths in a partition key. * * Currently, when a resource does not specify a value for the PartitionKey, * we assign a temporary value `PartitionKey.None` and later discern whether * it is a PartitionKey.Undefined or PartitionKey.Empty based on the Collection Type. + * We retain this behaviour for single path partition keys. * * For collections with multiple path keys, absence of a partition key values is * always treated as a PartitionKey.Undefined. */ - if (value == null - || value.partitionKeyValues.Count == 0 - || (value.partitionKeyValues.Count == 1 && object.ReferenceEquals(value.partitionKeyValues[0], PartitionKeyValueList.NoneType))) + if (valueList == null + || valueList.PartitionKeyValues.Count == 0 + || (valueList.PartitionKeyValues.Count == 1 && None.Equals(valueList.PartitionKeyValues[0]))) { - this.InternalKey = PartitionKey.None.InternalKey; + this.InternalKey = None.InternalKey; this.IsNone = true; } else { - object[] valueArray = new object[value.partitionKeyValues.Count]; + object[] valueArray = new object[valueList.PartitionKeyValues.Count]; int i = 0; - foreach (object val in value.partitionKeyValues) + foreach (object val in valueList.PartitionKeyValues) { - if (object.ReferenceEquals(PartitionKeyValueList.NoneType, val)) + if (None.Equals(val)) { valueArray[i++] = Undefined.Value; } diff --git a/Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs b/Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs index 5045c063fc..ccfaf5a17f 100644 --- a/Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs +++ b/Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs @@ -14,16 +14,14 @@ namespace Microsoft.Azure.Cosmos /// public sealed class PartitionKeyValueList { - internal readonly IList partitionKeyValues; - - internal static readonly object NoneType = new object(); + internal readonly IList PartitionKeyValues; /// /// Creates a new partition key value list object. /// public PartitionKeyValueList() { - this.partitionKeyValues = new List(); + this.PartitionKeyValues = new List(); } /// @@ -38,7 +36,7 @@ public void Add(string val) } else { - this.partitionKeyValues.Add(val); + this.PartitionKeyValues.Add(val); } } @@ -48,7 +46,7 @@ public void Add(string val) /// The value of type double to be used as partitionKey. public void Add(double val) { - this.partitionKeyValues.Add(val); + this.PartitionKeyValues.Add(val); } /// @@ -57,7 +55,7 @@ public void Add(double val) /// The value of type bool to be used as partitionKey. public void Add(bool val) { - this.partitionKeyValues.Add(val); + this.PartitionKeyValues.Add(val); } /// @@ -65,17 +63,15 @@ public void Add(bool val) /// public void AddNullValue() { - this.partitionKeyValues.Add(null); - } - - internal void AddUndefined() - { - this.partitionKeyValues.Add(Undefined.Value); + this.PartitionKeyValues.Add(null); } - internal void AddNoneType() + /// + /// Adds a None partition key value. + /// + public void AddNoneType() { - this.partitionKeyValues.Add(NoneType); + this.PartitionKeyValues.Add(PartitionKey.None); } } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosItemUnitTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosItemUnitTests.cs index 68fede732b..df83dc9824 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosItemUnitTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosItemUnitTests.cs @@ -388,15 +388,21 @@ public async Task AllowBatchingRequestsSendsToExecutor_Delete() [TestMethod] public async Task TestNestedPartitionKeyValueFromStreamAsync() { - ContainerInternal mockContainer = (ContainerInternal)MockCosmosUtil.CreateMockCosmosClient().GetContainer("TestDb", "Test"); + ContainerInternal originalContainer = (ContainerInternal)MockCosmosUtil.CreateMockCosmosClient().GetContainer("TestDb", "Test"); - Mock containerMock = new Mock(); - ContainerInternal container = containerMock.Object; + Mock mockedContainer = new Mock( + originalContainer.ClientContext, + (DatabaseInternal)originalContainer.Database, + originalContainer.Id, + null) + { + CallBase = true + }; - containerMock.Setup(e => e.GetPartitionKeyPathTokensAsync(It.IsAny())) - .Returns(Task.FromResult((IReadOnlyList) new List{ new string[] { "a", "b", "c" } })); - containerMock.Setup(x => x.GetPartitionKeyValueFromStreamAsync(It.IsAny(), It.IsAny())) - .Returns((stream, cancellationToken) => mockContainer.GetPartitionKeyValueFromStreamAsync(stream, cancellationToken)); + mockedContainer.Setup(e => e.GetPartitionKeyPathTokensAsync(It.IsAny())) + .Returns(Task.FromResult((IReadOnlyList)new List { new string[] { "a", "b", "c" }})); + + ContainerInternal containerWithMockPartitionKeyPath = mockedContainer.Object; List invalidNestedItems = new List { @@ -457,26 +463,31 @@ public async Task TestNestedPartitionKeyValueFromStreamAsync() foreach (dynamic poco in invalidNestedItems) { - object pk = await container.GetPartitionKeyValueFromStreamAsync( + object pk = await containerWithMockPartitionKeyPath.GetPartitionKeyValueFromStreamAsync( MockCosmosUtil.Serializer.ToStream(poco), default(CancellationToken)); - Assert.IsTrue(object.ReferenceEquals(Cosmos.PartitionKey.None, pk) || object.Equals(Cosmos.PartitionKey.None, pk)); + Assert.IsTrue(object.Equals(Cosmos.PartitionKey.None, pk)); } } - [Ignore] [TestMethod] public async Task TestMultipleNestedPartitionKeyValueFromStreamAsync() { - ContainerInternal mockContainer = (ContainerInternal)MockCosmosUtil.CreateMockCosmosClient().GetContainer("TestDb", "Test"); + ContainerInternal originalContainer = (ContainerInternal)MockCosmosUtil.CreateMockCosmosClient().GetContainer("TestDb", "Test"); - Mock containerMock = new Mock() { CallBase = true }; - ContainerInternal container = containerMock.Object; + Mock mockedContainer = new Mock( + originalContainer.ClientContext, + (DatabaseInternal)originalContainer.Database, + originalContainer.Id, + null) + { + CallBase = true + }; - containerMock.Setup(e => e.GetPartitionKeyPathTokensAsync(It.IsAny())) + mockedContainer.Setup(e => e.GetPartitionKeyPathTokensAsync(It.IsAny())) .Returns(Task.FromResult((IReadOnlyList)new List { new string[] { "a", "b", "c" }, new string[] { "a","e","f" } })); - containerMock.Setup(x => x.GetPartitionKeyValueFromStreamAsync(It.IsAny(), It.IsAny())) - .Returns((stream, cancellationToken) => mockContainer.GetPartitionKeyValueFromStreamAsync(stream, cancellationToken)); + + ContainerInternal containerWithMockPartitionKeyPath = mockedContainer.Object; List validNestedItems = new List { @@ -564,7 +575,7 @@ public async Task TestMultipleNestedPartitionKeyValueFromStreamAsync() foreach (dynamic poco in validNestedItems) { - Cosmos.PartitionKey pk = await container.GetPartitionKeyValueFromStreamAsync( + Cosmos.PartitionKey pk = await containerWithMockPartitionKeyPath.GetPartitionKeyValueFromStreamAsync( MockCosmosUtil.Serializer.ToStream(poco.Item1), default(CancellationToken)); string abc = pk.InternalKey.ToJsonString(); From c81b04723434be900f88339b9726e0931ae6aec3 Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Thu, 16 Jul 2020 01:01:06 -0700 Subject: [PATCH 14/36] Implementing suggestions from PR review. --- Microsoft.Azure.Cosmos/src/PartitionKey.cs | 36 ++++++++++--------- .../Resource/Container/ContainerCore.Items.cs | 14 ++++---- .../Resource/Settings/ContainerProperties.cs | 11 ++++-- .../NameRoutingTests.cs | 28 ++++++++++++--- .../CosmosItemUnitTests.cs | 4 +-- 5 files changed, 59 insertions(+), 34 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/PartitionKey.cs b/Microsoft.Azure.Cosmos/src/PartitionKey.cs index b56e928f4f..3138244372 100644 --- a/Microsoft.Azure.Cosmos/src/PartitionKey.cs +++ b/Microsoft.Azure.Cosmos/src/PartitionKey.cs @@ -103,18 +103,16 @@ internal PartitionKey(object value) /// An object of Type PartitionKeyValueList which supports multiple partition key paths. public PartitionKey(PartitionKeyValueList valueList) { - /* - * Why these checks? - * These changes are being added for SDK to support multiple paths in a partition key. - * - * Currently, when a resource does not specify a value for the PartitionKey, - * we assign a temporary value `PartitionKey.None` and later discern whether - * it is a PartitionKey.Undefined or PartitionKey.Empty based on the Collection Type. - * We retain this behaviour for single path partition keys. - * - * For collections with multiple path keys, absence of a partition key values is - * always treated as a PartitionKey.Undefined. - */ + // Why these checks? + // These changes are being added for SDK to support multiple paths in a partition key. + // + // Currently, when a resource does not specify a value for the PartitionKey, + // we assign a temporary value `PartitionKey.None` and later discern whether + // it is a PartitionKey.Undefined or PartitionKey.Empty based on the Collection Type. + // We retain this behaviour for single path partition keys. + // + // For collections with multiple path keys, absence of a partition key values is + // always treated as a PartitionKey.Undefined. if (valueList == null || valueList.PartitionKeyValues.Count == 0 || (valueList.PartitionKeyValues.Count == 1 && None.Equals(valueList.PartitionKeyValues[0]))) @@ -125,18 +123,19 @@ public PartitionKey(PartitionKeyValueList valueList) else { object[] valueArray = new object[valueList.PartitionKeyValues.Count]; - int i = 0; - foreach (object val in valueList.PartitionKeyValues) + for (int i = 0; i < valueList.PartitionKeyValues.Count; i++) { + object val = valueList.PartitionKeyValues[i]; if (None.Equals(val)) { - valueArray[i++] = Undefined.Value; + valueArray[i] = Undefined.Value; } else { - valueArray[i++] = val; + valueArray[i] = val; } } + this.InternalKey = new Documents.PartitionKey(valueArray).InternalKey; this.IsNone = false; } @@ -191,6 +190,11 @@ public bool Equals(PartitionKey other) { PartitionKeyInternal partitionKeyInternal = this.InternalKey; PartitionKeyInternal otherPartitionKeyInternal = other.InternalKey; + if ((partitionKeyInternal == null || otherPartitionKeyInternal == null) && partitionKeyInternal != otherPartitionKeyInternal) + { + return false; + } + if (partitionKeyInternal == null) { partitionKeyInternal = PartitionKey.NullPartitionKeyInternal; diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index db6b82127f..b259c0e49d 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -627,7 +627,7 @@ private async Task ExtractPartitionKeyAndProcessItemStreamAsync if (partitionKey.HasValue) { PartitionKeyDefinition pKeyDefinition = await this.GetPartitionKeyDefinitionAsync(); - if (((PartitionKey)partitionKey).InternalKey.Components.Count != pKeyDefinition.Paths.Count) + if (partitionKey.HasValue && partitionKey.Value.InternalKey.Components.Count != pKeyDefinition.Paths.Count) { throw new ArgumentException(RMResources.MissingPartitionKeyValue); } @@ -742,14 +742,12 @@ public override async Task GetPartitionKeyValueFromStreamAsync( IReadOnlyList tokenslist = await this.GetPartitionKeyPathTokensAsync(cancellation); CosmosElement[] cosmosElementArray = new CosmosElement[tokenslist.Count]; - int index = 0; - foreach (string[] tokens in tokenslist) + for (int index = 0; index < tokenslist.Count; index++) { - CosmosElement partitionKeyValue = this.ParseTokenListForElement(pathTraversal, tokens); - cosmosElementArray[index++] = partitionKeyValue; + cosmosElementArray[index] = ParseTokenListForElement(pathTraversal, tokenslist[index]); } - return this.CosmosElementToPartitionKeyObject(cosmosElementArray); + return CosmosElementToPartitionKeyObject(cosmosElementArray); } finally { @@ -758,7 +756,7 @@ public override async Task GetPartitionKeyValueFromStreamAsync( } } - private CosmosElement ParseTokenListForElement(CosmosObject pathTraversal, string[] tokens) + private static CosmosElement ParseTokenListForElement(CosmosObject pathTraversal, string[] tokens) { CosmosElement partitionKeyValue; @@ -778,7 +776,7 @@ private CosmosElement ParseTokenListForElement(CosmosObject pathTraversal, strin return partitionKeyValue; } - private PartitionKey CosmosElementToPartitionKeyObject(CosmosElement[] cosmosElementArray) + private static PartitionKey CosmosElementToPartitionKeyObject(CosmosElement[] cosmosElementArray) { PartitionKeyValueList partitionKeyValueList = new PartitionKeyValueList(); diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs index 7b4c34d867..0c15f2aced 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs @@ -520,6 +520,11 @@ internal IReadOnlyList PartitionKeyPathTokens return this.partitionKeyPathTokens; } + if (this.PartitionKey == null) + { + throw new ArgumentNullException(nameof(this.PartitionKey)); + } + if (this.PartitionKey.Paths.Count > 1 && this.PartitionKey.Kind != PartitionKind.MultiHash) { throw new NotImplementedException("PartitionKey extraction with composite partition keys not supported."); @@ -530,10 +535,10 @@ internal IReadOnlyList PartitionKeyPathTokens throw new ArgumentOutOfRangeException($"Container {this.Id} is not partitioned"); } - Collection partitionKeyPathTokensList = new Collection(); - foreach (string s in this.PartitionKey?.Paths) + List partitionKeyPathTokensList = new List(); + foreach (string path in this.PartitionKey?.Paths) { - partitionKeyPathTokensList.Add(s.Split(ContainerProperties.partitionKeyTokenDelimeter, StringSplitOptions.RemoveEmptyEntries)); + partitionKeyPathTokensList.Add(path.Split(ContainerProperties.partitionKeyTokenDelimeter, StringSplitOptions.RemoveEmptyEntries)); } this.partitionKeyPathTokens = partitionKeyPathTokensList; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs index 18aabedcc5..3113c48fd3 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs @@ -1825,18 +1825,27 @@ public async Task VerifyDocumentCrudWithMultiHashKind() try { doc1 = new Document { Id = "doc1" }; - doc1.SetValue("Zipcode", "11790"); - Cosmos.PartitionKey pKeyErr = new Cosmos.PartitionKey(new Object[] { doc1.GetPropertyValue("ZipCode") }); + doc1.SetValue("Zipcode", 11790); + + PartitionKeyValueList pKValueList = new PartitionKeyValueList(); + pKValueList.Add(doc1.GetPropertyValue("ZipCode")); + + Cosmos.PartitionKey pKeyErr = new Cosmos.PartitionKey(pKValueList); await container.CreateItemAsync(doc1, pKeyErr); } catch (ArgumentException ar) { Assert.AreEqual(ar.Message, RMResources.PartitionKeyMismatch); } + //Document Read. foreach (Document document in documents) { - Cosmos.PartitionKey pKey = new Cosmos.PartitionKey(new object[] { document.GetValue("ZipCode"), document.GetPropertyValue("Address") }); + PartitionKeyValueList pKValueList = new PartitionKeyValueList(); + pKValueList.Add(document.GetPropertyValue("ZipCode")); + pKValueList.Add(document.GetPropertyValue("Address")); + Cosmos.PartitionKey pKey = new Cosmos.PartitionKey(pKValueList); + Document readDocument = (await container.ReadItemAsync(document.Id, pKey)).Resource; Assert.AreEqual(document.ToString(), readDocument.ToString()); } @@ -1844,9 +1853,14 @@ public async Task VerifyDocumentCrudWithMultiHashKind() //Document Update. foreach (ItemResponse obj in documents) { - Cosmos.PartitionKey pKey = new Cosmos.PartitionKey(new object[] { obj.Resource.GetValue("ZipCode"), obj.Resource.GetPropertyValue("Address") }); + PartitionKeyValueList pKValueList = new PartitionKeyValueList(); + pKValueList.Add(obj.Resource.GetValue("ZipCode")); + pKValueList.Add(obj.Resource.GetPropertyValue("Address")); + Cosmos.PartitionKey pKey = new Cosmos.PartitionKey(pKValueList); + Document document = (await container.ReadItemAsync(obj.Resource.Id, pKey)).Resource; document.SetPropertyValue("Name", document.Id); + Document readDocument = (await container.ReplaceItemAsync(document, document.Id, pKey)).Resource; Assert.AreEqual(readDocument.GetValue("Name"), document.GetValue("Name")); } @@ -1854,7 +1868,11 @@ public async Task VerifyDocumentCrudWithMultiHashKind() //Document Delete. foreach (Document document in documents) { - Cosmos.PartitionKey pKey = new Cosmos.PartitionKey(new object[] { document.GetValue("ZipCode"), document.GetPropertyValue("Address") }); + PartitionKeyValueList pKValueList = new PartitionKeyValueList(); + pKValueList.Add(document.GetPropertyValue("ZipCode")); + pKValueList.Add(document.GetPropertyValue("Address")); + Cosmos.PartitionKey pKey = new Cosmos.PartitionKey(pKValueList); + Document readDocument = (await container.DeleteItemAsync(document.Id, pKey)).Resource; try { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosItemUnitTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosItemUnitTests.cs index df83dc9824..2ece3d6786 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosItemUnitTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosItemUnitTests.cs @@ -578,8 +578,8 @@ public async Task TestMultipleNestedPartitionKeyValueFromStreamAsync() Cosmos.PartitionKey pk = await containerWithMockPartitionKeyPath.GetPartitionKeyValueFromStreamAsync( MockCosmosUtil.Serializer.ToStream(poco.Item1), default(CancellationToken)); - string abc = pk.InternalKey.ToJsonString(); - Assert.AreEqual(abc,poco.Item2); + string partitionKeyString = pk.InternalKey.ToJsonString(); + Assert.AreEqual(poco.Item2, partitionKeyString); } } From 5574bed54088416efdb64781933da30ef8d380df Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Thu, 16 Jul 2020 01:14:06 -0700 Subject: [PATCH 15/36] Misc cleanup --- Microsoft.Azure.Cosmos/src/PartitionKey.cs | 5 ----- Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs | 9 +-------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/PartitionKey.cs b/Microsoft.Azure.Cosmos/src/PartitionKey.cs index 3138244372..30f8d10acd 100644 --- a/Microsoft.Azure.Cosmos/src/PartitionKey.cs +++ b/Microsoft.Azure.Cosmos/src/PartitionKey.cs @@ -190,11 +190,6 @@ public bool Equals(PartitionKey other) { PartitionKeyInternal partitionKeyInternal = this.InternalKey; PartitionKeyInternal otherPartitionKeyInternal = other.InternalKey; - if ((partitionKeyInternal == null || otherPartitionKeyInternal == null) && partitionKeyInternal != otherPartitionKeyInternal) - { - return false; - } - if (partitionKeyInternal == null) { partitionKeyInternal = PartitionKey.NullPartitionKeyInternal; diff --git a/Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs b/Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs index ccfaf5a17f..c21d6fc1b3 100644 --- a/Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs +++ b/Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs @@ -30,14 +30,7 @@ public PartitionKeyValueList() /// The value of type string to be used as partitionKey. public void Add(string val) { - if (val == null) - { - this.AddNullValue(); - } - else - { - this.PartitionKeyValues.Add(val); - } + this.PartitionKeyValues.Add(val); } /// From 3a679c9f3b67da71a8a12ad3f5c1ba36f24d9ee6 Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Fri, 17 Jul 2020 16:01:14 -0700 Subject: [PATCH 16/36] Suggestions based on the internal review meeting. --- Microsoft.Azure.Cosmos/src/PartitionKey.cs | 66 +------- .../src/PartitionKeyBuilder.cs | 156 ++++++++++++++++++ .../src/PartitionKeyValueList.cs | 70 -------- .../Resource/Container/ContainerCore.Items.cs | 14 +- .../Resource/Settings/ContainerProperties.cs | 21 ++- .../src/Resource/Settings/PartitionKind.cs | 29 ++++ .../NameRoutingTests.cs | 28 ++-- 7 files changed, 227 insertions(+), 157 deletions(-) create mode 100644 Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs delete mode 100644 Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs create mode 100644 Microsoft.Azure.Cosmos/src/Resource/Settings/PartitionKind.cs diff --git a/Microsoft.Azure.Cosmos/src/PartitionKey.cs b/Microsoft.Azure.Cosmos/src/PartitionKey.cs index 30f8d10acd..cb512c22ad 100644 --- a/Microsoft.Azure.Cosmos/src/PartitionKey.cs +++ b/Microsoft.Azure.Cosmos/src/PartitionKey.cs @@ -77,76 +77,12 @@ public PartitionKey(bool partitionKeyValue) this.IsNone = false; } - /// - /// Creates a new partition key value. - /// - /// The value to use as partition key. - public PartitionKey(double partitionKeyValue) - { - this.InternalKey = new Documents.PartitionKey(partitionKeyValue).InternalKey; - this.IsNone = false; - } - - /// - /// Creates a new partition key value. - /// - /// The value to use as partition key. - internal PartitionKey(object value) - { - this.InternalKey = new Documents.PartitionKey(value).InternalKey; - this.IsNone = false; - } - - /// - /// Creates a new partition key value. - /// - /// An object of Type PartitionKeyValueList which supports multiple partition key paths. - public PartitionKey(PartitionKeyValueList valueList) - { - // Why these checks? - // These changes are being added for SDK to support multiple paths in a partition key. - // - // Currently, when a resource does not specify a value for the PartitionKey, - // we assign a temporary value `PartitionKey.None` and later discern whether - // it is a PartitionKey.Undefined or PartitionKey.Empty based on the Collection Type. - // We retain this behaviour for single path partition keys. - // - // For collections with multiple path keys, absence of a partition key values is - // always treated as a PartitionKey.Undefined. - if (valueList == null - || valueList.PartitionKeyValues.Count == 0 - || (valueList.PartitionKeyValues.Count == 1 && None.Equals(valueList.PartitionKeyValues[0]))) - { - this.InternalKey = None.InternalKey; - this.IsNone = true; - } - else - { - object[] valueArray = new object[valueList.PartitionKeyValues.Count]; - for (int i = 0; i < valueList.PartitionKeyValues.Count; i++) - { - object val = valueList.PartitionKeyValues[i]; - if (None.Equals(val)) - { - valueArray[i] = Undefined.Value; - } - else - { - valueArray[i] = val; - } - } - - this.InternalKey = new Documents.PartitionKey(valueArray).InternalKey; - this.IsNone = false; - } - } - /// /// Creates a new partition key value. /// /// The value to use as partition key. /// The value to decide partitionKey is None. - private PartitionKey(PartitionKeyInternal partitionKeyInternal, bool isNone = false) + internal PartitionKey(PartitionKeyInternal partitionKeyInternal, bool isNone = false) { this.InternalKey = partitionKeyInternal; this.IsNone = isNone; diff --git a/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs b/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs new file mode 100644 index 0000000000..810a137b9c --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs @@ -0,0 +1,156 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ +namespace Microsoft.Azure.Cosmos +{ + using System; + using System.Collections; + using System.Collections.Generic; + using Microsoft.Azure.Documents; + using Microsoft.Azure.Documents.Routing; + + /// + /// Represents a partition key value list in the Azure Cosmos DB service. + /// + public sealed class PartitionKeyBuilder + { + private readonly IList partitionKeyValues; + + private bool isBuilt = false; + + /// + /// Creates a new partition key value list object. + /// + public PartitionKeyBuilder() + { + if (this.isBuilt) + { + throw new InvalidOperationException("This builder instance has already been used to build a PartitionKey. Create a new instance to build another."); + } + + this.partitionKeyValues = new List(); + } + + /// + /// Adds a partition key value of type string to the list. + /// + /// The value of type string to be used as partition key. + /// An instance of to use. + public PartitionKeyBuilder Add(string val) + { + if (this.isBuilt) + { + throw new InvalidOperationException("This builder instance has already been used to build a PartitionKey. Create a new instance to build another."); + } + + this.partitionKeyValues.Add(val); + return this; + } + + /// + /// Adds a partition key value of type double to the list. + /// + /// The value of type double to be used as partition key. + /// An instance of to use. + public PartitionKeyBuilder Add(double val) + { + if (this.isBuilt) + { + throw new InvalidOperationException("This builder instance has already been used to build a PartitionKey. Create a new instance to build another."); + } + + this.partitionKeyValues.Add(val); + return this; + } + + /// + /// Adds a partition key value of type bool to the list. + /// + /// The value of type bool to be used as partition key. + /// An instance of to use. + public PartitionKeyBuilder Add(bool val) + { + if (this.isBuilt) + { + throw new InvalidOperationException("This builder instance has already been used to build a PartitionKey. Create a new instance to build another."); + } + + this.partitionKeyValues.Add(val); + return this; + } + + /// + /// Adds a partition key value which is null + /// + /// An instance of to use. + public PartitionKeyBuilder AddNullValue() + { + if (this.isBuilt) + { + throw new InvalidOperationException("This builder instance has already been used to build a PartitionKey. Create a new instance to build another."); + } + + this.partitionKeyValues.Add(null); + return this; + } + + /// + /// Adds a None partition key value. + /// + /// An instance of to use. + public PartitionKeyBuilder AddNoneType() + { + if (this.isBuilt) + { + throw new InvalidOperationException("This builder instance has already been used to build a PartitionKey. Create a new instance to build another."); + } + + this.partitionKeyValues.Add(PartitionKey.None); + return this; + } + + /// + /// Builds a new instance of the with the specified Partition Key values. + /// + /// An instance of + public PartitionKey Build() + { + if (this.isBuilt) + { + throw new InvalidOperationException("This builder instance has already been used to build a PartitionKey. Create a new instance to build another."); + } + + PartitionKeyInternal partitionKeyInternal; + bool isNone; + + if (this.partitionKeyValues.Count == 0 + || (this.partitionKeyValues.Count == 1 && PartitionKey.None.Equals(this.partitionKeyValues[0]))) + { + partitionKeyInternal = PartitionKey.None.InternalKey; + isNone = true; + } + else + { + object[] valueArray = new object[this.partitionKeyValues.Count]; + for (int i = 0; i < this.partitionKeyValues.Count; i++) + { + object val = this.partitionKeyValues[i]; + if (PartitionKey.None.Equals(val)) + { + valueArray[i] = Undefined.Value; + } + else + { + valueArray[i] = val; + } + } + + partitionKeyInternal = new Documents.PartitionKey(valueArray).InternalKey; + isNone = false; + } + + this.isBuilt = true; + return new PartitionKey(partitionKeyInternal, isNone); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs b/Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs deleted file mode 100644 index c21d6fc1b3..0000000000 --- a/Microsoft.Azure.Cosmos/src/PartitionKeyValueList.cs +++ /dev/null @@ -1,70 +0,0 @@ -//------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -//------------------------------------------------------------ -namespace Microsoft.Azure.Cosmos -{ - using System; - using System.Collections; - using System.Collections.Generic; - using Microsoft.Azure.Documents; - using Microsoft.Azure.Documents.Routing; - - /// - /// Represents a partition key value list in the Azure Cosmos DB service. - /// - public sealed class PartitionKeyValueList - { - internal readonly IList PartitionKeyValues; - - /// - /// Creates a new partition key value list object. - /// - public PartitionKeyValueList() - { - this.PartitionKeyValues = new List(); - } - - /// - /// Adds a partition key value of type string to the list. - /// - /// The value of type string to be used as partitionKey. - public void Add(string val) - { - this.PartitionKeyValues.Add(val); - } - - /// - /// Adds a partition key value of type double to the list. - /// - /// The value of type double to be used as partitionKey. - public void Add(double val) - { - this.PartitionKeyValues.Add(val); - } - - /// - /// Adds a partition key value of type bool to the list. - /// - /// The value of type bool to be used as partitionKey. - public void Add(bool val) - { - this.PartitionKeyValues.Add(val); - } - - /// - /// Adds a partition key value which is null - /// - public void AddNullValue() - { - this.PartitionKeyValues.Add(null); - } - - /// - /// Adds a None partition key value. - /// - public void AddNoneType() - { - this.PartitionKeyValues.Add(PartitionKey.None); - } - } -} \ 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 3eb3f20450..d6620b607e 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -783,13 +783,13 @@ private static CosmosElement ParseTokenListForElement(CosmosObject pathTraversal private static PartitionKey CosmosElementToPartitionKeyObject(CosmosElement[] cosmosElementArray) { - PartitionKeyValueList partitionKeyValueList = new PartitionKeyValueList(); + PartitionKeyBuilder partitionKeyBuilder = new PartitionKeyBuilder(); foreach (CosmosElement cosmosElement in cosmosElementArray) { if (cosmosElement == null) { - partitionKeyValueList.AddNoneType(); + partitionKeyBuilder.AddNoneType(); } else { @@ -798,22 +798,22 @@ private static PartitionKey CosmosElementToPartitionKeyObject(CosmosElement[] co { case CosmosElementType.String: CosmosString cosmosString = cosmosElement as CosmosString; - partitionKeyValueList.Add(cosmosString.Value); + partitionKeyBuilder.Add(cosmosString.Value); break; case CosmosElementType.Number: CosmosNumber cosmosNumber = cosmosElement as CosmosNumber; double value = Number64.ToDouble(cosmosNumber.Value); - partitionKeyValueList.Add(value); + partitionKeyBuilder.Add(value); break; case CosmosElementType.Boolean: CosmosBoolean cosmosBool = cosmosElement as CosmosBoolean; - partitionKeyValueList.Add(cosmosBool.Value); + partitionKeyBuilder.Add(cosmosBool.Value); break; case CosmosElementType.Null: - partitionKeyValueList.AddNullValue(); + partitionKeyBuilder.AddNullValue(); break; default: @@ -823,7 +823,7 @@ private static PartitionKey CosmosElementToPartitionKeyObject(CosmosElement[] co } } - return new PartitionKey(partitionKeyValueList); + return partitionKeyBuilder.Build(); } private string GetResourceUri(RequestOptions requestOptions, OperationType operationType, string itemId) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs index 0c15f2aced..30eac79ba0 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs @@ -94,6 +94,25 @@ public ContainerProperties(string id, string partitionKeyPath) this.ValidateRequiredProperties(); } + /// + /// Initializes a new instance of the class for the Azure Cosmos DB service. + /// + /// The Id of the resource in the Azure Cosmos service. + /// The path to the partition key. Example: /location + /// The value of Partition Kind to be used. + public ContainerProperties(string id, IList partitionKeyPaths, Cosmos.PartitionKind partitionKind) + { + this.Id = id; + this.PartitionKey = new PartitionKeyDefinition + { + Paths = new Collection(partitionKeyPaths), + Kind = (Documents.PartitionKind)partitionKind, + Version = Documents.PartitionKeyDefinitionVersion.V2 + }; + + this.ValidateRequiredProperties(); + } + /// /// Gets or sets the /// @@ -525,7 +544,7 @@ internal IReadOnlyList PartitionKeyPathTokens throw new ArgumentNullException(nameof(this.PartitionKey)); } - if (this.PartitionKey.Paths.Count > 1 && this.PartitionKey.Kind != PartitionKind.MultiHash) + if (this.PartitionKey.Paths.Count > 1 && this.PartitionKey.Kind != Documents.PartitionKind.MultiHash) { throw new NotImplementedException("PartitionKey extraction with composite partition keys not supported."); } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/PartitionKind.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/PartitionKind.cs new file mode 100644 index 0000000000..f8176e9354 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Resource/Settings/PartitionKind.cs @@ -0,0 +1,29 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos +{ + /// + /// Partitioning version. + /// + public enum PartitionKind + { + /// + /// Original version of hash partitioning. + /// + Hash = 1, + + /// + /// Range partitioning + /// + Range = 2, + + /// + /// Enhanced version of hash partitioning - Offers partitioning over multiple paths. + /// + /// This version is available in newer SDKs only. + MultiHash = 3, + + } +} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs index 3113c48fd3..a4cb34a94f 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs @@ -1827,10 +1827,10 @@ public async Task VerifyDocumentCrudWithMultiHashKind() doc1 = new Document { Id = "doc1" }; doc1.SetValue("Zipcode", 11790); - PartitionKeyValueList pKValueList = new PartitionKeyValueList(); + PartitionKeyBuilder pKValueList = new PartitionKeyBuilder(); pKValueList.Add(doc1.GetPropertyValue("ZipCode")); - Cosmos.PartitionKey pKeyErr = new Cosmos.PartitionKey(pKValueList); + Cosmos.PartitionKey pKeyErr = pKValueList.Build(); await container.CreateItemAsync(doc1, pKeyErr); } catch (ArgumentException ar) @@ -1841,10 +1841,10 @@ public async Task VerifyDocumentCrudWithMultiHashKind() //Document Read. foreach (Document document in documents) { - PartitionKeyValueList pKValueList = new PartitionKeyValueList(); - pKValueList.Add(document.GetPropertyValue("ZipCode")); - pKValueList.Add(document.GetPropertyValue("Address")); - Cosmos.PartitionKey pKey = new Cosmos.PartitionKey(pKValueList); + Cosmos.PartitionKey pKey = new PartitionKeyBuilder() + .Add(document.GetPropertyValue("ZipCode")) + .Add(document.GetPropertyValue("Address")) + .Build(); Document readDocument = (await container.ReadItemAsync(document.Id, pKey)).Resource; Assert.AreEqual(document.ToString(), readDocument.ToString()); @@ -1853,10 +1853,10 @@ public async Task VerifyDocumentCrudWithMultiHashKind() //Document Update. foreach (ItemResponse obj in documents) { - PartitionKeyValueList pKValueList = new PartitionKeyValueList(); - pKValueList.Add(obj.Resource.GetValue("ZipCode")); - pKValueList.Add(obj.Resource.GetPropertyValue("Address")); - Cosmos.PartitionKey pKey = new Cosmos.PartitionKey(pKValueList); + Cosmos.PartitionKey pKey = new PartitionKeyBuilder() + .Add(obj.Resource.GetValue("ZipCode")) + .Add(obj.Resource.GetPropertyValue("Address")) + .Build(); Document document = (await container.ReadItemAsync(obj.Resource.Id, pKey)).Resource; document.SetPropertyValue("Name", document.Id); @@ -1868,10 +1868,10 @@ public async Task VerifyDocumentCrudWithMultiHashKind() //Document Delete. foreach (Document document in documents) { - PartitionKeyValueList pKValueList = new PartitionKeyValueList(); - pKValueList.Add(document.GetPropertyValue("ZipCode")); - pKValueList.Add(document.GetPropertyValue("Address")); - Cosmos.PartitionKey pKey = new Cosmos.PartitionKey(pKValueList); + Cosmos.PartitionKey pKey = new PartitionKeyBuilder() + .Add(document.GetPropertyValue("ZipCode")) + .Add(document.GetPropertyValue("Address")) + .Build(); Document readDocument = (await container.DeleteItemAsync(document.Id, pKey)).Resource; try From cd07652a7ec325707eb1f40e2efdbeaf8260b43b Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Fri, 17 Jul 2020 16:03:33 -0700 Subject: [PATCH 17/36] Undo one change. --- Microsoft.Azure.Cosmos/src/PartitionKey.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Microsoft.Azure.Cosmos/src/PartitionKey.cs b/Microsoft.Azure.Cosmos/src/PartitionKey.cs index cb512c22ad..5b93a51950 100644 --- a/Microsoft.Azure.Cosmos/src/PartitionKey.cs +++ b/Microsoft.Azure.Cosmos/src/PartitionKey.cs @@ -77,6 +77,26 @@ public PartitionKey(bool partitionKeyValue) this.IsNone = false; } + /// + /// Creates a new partition key value. + /// + /// The value to use as partition key. + public PartitionKey(double partitionKeyValue) + { + this.InternalKey = new Documents.PartitionKey(partitionKeyValue).InternalKey; + this.IsNone = false; + } + + /// + /// Creates a new partition key value. + /// + /// The value to use as partition key. + internal PartitionKey(object value) + { + this.InternalKey = new Documents.PartitionKey(value).InternalKey; + this.IsNone = false; + } + /// /// Creates a new partition key value. /// From fac9372c752198800cb9fa627dc00602eb2223bd Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Mon, 20 Jul 2020 11:07:34 -0700 Subject: [PATCH 18/36] Moving changes to inside a internal directive --- .../Resource/Settings/ContainerProperties.cs | 25 +++++++++++++++++++ .../src/Resource/Settings/PartitionKind.cs | 7 +++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs index 30eac79ba0..938ef2dba5 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs @@ -94,6 +94,7 @@ public ContainerProperties(string id, string partitionKeyPath) this.ValidateRequiredProperties(); } +#if INTERNAL /// /// Initializes a new instance of the class for the Azure Cosmos DB service. /// @@ -112,6 +113,7 @@ public ContainerProperties(string id, IList partitionKeyPaths, Cosmos.Pa this.ValidateRequiredProperties(); } +#endif /// /// Gets or sets the @@ -308,6 +310,29 @@ public string PartitionKeyPath } } +#if INTERNAL + /// + /// JSON path used for containers partitioning + /// + [JsonIgnore] + public IList PartitionKeyPaths + { + get => this.PartitionKey?.Paths != null && this.PartitionKey.Paths.Count > 0 ? this.PartitionKey?.Paths : null; + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(this.PartitionKeyPath)); + } + + this.PartitionKey = new PartitionKeyDefinition + { + Paths = new Collection(value) + }; + } + } +#endif + /// /// Gets or sets the time to live base time stamp property path. /// diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/PartitionKind.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/PartitionKind.cs index f8176e9354..0d5e8a5e36 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Settings/PartitionKind.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Settings/PartitionKind.cs @@ -1,16 +1,16 @@ //------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ - +#if INTERNAL namespace Microsoft.Azure.Cosmos { /// - /// Partitioning version. + /// Partition kind. /// public enum PartitionKind { /// - /// Original version of hash partitioning. + /// Original and default Partition Kind. /// Hash = 1, @@ -27,3 +27,4 @@ public enum PartitionKind } } +#endif \ No newline at end of file From 3cd5172f58cd2035b4fb5da01aa781364ffc7107 Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Wed, 22 Jul 2020 03:32:16 -0700 Subject: [PATCH 19/36] Saving changes. --- Microsoft.Azure.Cosmos/src/CosmosClient.cs | 2 +- .../src/PartitionKeyBuilder.cs | 20 +--- .../Resource/Settings/ContainerProperties.cs | 9 +- .../src/Resource/Settings/PartitionKind.cs | 30 ----- .../CosmosItemTests.cs | 105 +++++++++++++++++ .../NameRoutingTests.cs | 109 ------------------ 6 files changed, 114 insertions(+), 161 deletions(-) delete mode 100644 Microsoft.Azure.Cosmos/src/Resource/Settings/PartitionKind.cs diff --git a/Microsoft.Azure.Cosmos/src/CosmosClient.cs b/Microsoft.Azure.Cosmos/src/CosmosClient.cs index 63957bbf5c..c230f5f809 100644 --- a/Microsoft.Azure.Cosmos/src/CosmosClient.cs +++ b/Microsoft.Azure.Cosmos/src/CosmosClient.cs @@ -99,7 +99,7 @@ public class CosmosClient : IDisposable static CosmosClient() { - HttpConstants.Versions.CurrentVersion = HttpConstants.Versions.v2018_12_31; + HttpConstants.Versions.CurrentVersion = HttpConstants.Versions.v2020_07_15; HttpConstants.Versions.CurrentVersionUTF8 = Encoding.UTF8.GetBytes(HttpConstants.Versions.CurrentVersion); // V3 always assumes assemblies exists diff --git a/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs b/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs index 810a137b9c..975f775381 100644 --- a/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs +++ b/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs @@ -23,11 +23,6 @@ public sealed class PartitionKeyBuilder /// public PartitionKeyBuilder() { - if (this.isBuilt) - { - throw new InvalidOperationException("This builder instance has already been used to build a PartitionKey. Create a new instance to build another."); - } - this.partitionKeyValues = new List(); } @@ -120,17 +115,16 @@ public PartitionKey Build() throw new InvalidOperationException("This builder instance has already been used to build a PartitionKey. Create a new instance to build another."); } - PartitionKeyInternal partitionKeyInternal; - bool isNone; - if (this.partitionKeyValues.Count == 0 || (this.partitionKeyValues.Count == 1 && PartitionKey.None.Equals(this.partitionKeyValues[0]))) { - partitionKeyInternal = PartitionKey.None.InternalKey; - isNone = true; + this.isBuilt = true; + return PartitionKey.None; } else { + PartitionKeyInternal partitionKeyInternal; + object[] valueArray = new object[this.partitionKeyValues.Count]; for (int i = 0; i < this.partitionKeyValues.Count; i++) { @@ -146,11 +140,9 @@ public PartitionKey Build() } partitionKeyInternal = new Documents.PartitionKey(valueArray).InternalKey; - isNone = false; + this.isBuilt = true; + return new PartitionKey(partitionKeyInternal, false); } - - this.isBuilt = true; - return new PartitionKey(partitionKeyInternal, isNone); } } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs index 938ef2dba5..813f63407b 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs @@ -94,26 +94,23 @@ public ContainerProperties(string id, string partitionKeyPath) this.ValidateRequiredProperties(); } -#if INTERNAL /// /// Initializes a new instance of the class for the Azure Cosmos DB service. /// /// The Id of the resource in the Azure Cosmos service. /// The path to the partition key. Example: /location - /// The value of Partition Kind to be used. - public ContainerProperties(string id, IList partitionKeyPaths, Cosmos.PartitionKind partitionKind) + public ContainerProperties(string id, IList partitionKeyPaths) { this.Id = id; this.PartitionKey = new PartitionKeyDefinition { Paths = new Collection(partitionKeyPaths), - Kind = (Documents.PartitionKind)partitionKind, + Kind = Documents.PartitionKind.MultiHash, Version = Documents.PartitionKeyDefinitionVersion.V2 }; this.ValidateRequiredProperties(); } -#endif /// /// Gets or sets the @@ -310,7 +307,6 @@ public string PartitionKeyPath } } -#if INTERNAL /// /// JSON path used for containers partitioning /// @@ -331,7 +327,6 @@ public IList PartitionKeyPaths }; } } -#endif /// /// Gets or sets the time to live base time stamp property path. diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/PartitionKind.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/PartitionKind.cs deleted file mode 100644 index 0d5e8a5e36..0000000000 --- a/Microsoft.Azure.Cosmos/src/Resource/Settings/PartitionKind.cs +++ /dev/null @@ -1,30 +0,0 @@ -//------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -//------------------------------------------------------------ -#if INTERNAL -namespace Microsoft.Azure.Cosmos -{ - /// - /// Partition kind. - /// - public enum PartitionKind - { - /// - /// Original and default Partition Kind. - /// - Hash = 1, - - /// - /// Range partitioning - /// - Range = 2, - - /// - /// Enhanced version of hash partitioning - Offers partitioning over multiple paths. - /// - /// This version is available in newer SDKs only. - MultiHash = 3, - - } -} -#endif \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs index d1cf0dc89a..d89c9fd45a 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs @@ -1806,6 +1806,111 @@ public async Task CustomPropertiesItemRequestOptionsTest() Assert.AreEqual(HttpStatusCode.Created, responseAstype.StatusCode); } + [Ignore] //Ignoring this test until EnableSubpartitioning is set to true in BE. + [TestMethod] + public async Task VerifyDocumentCrudWithMultiHashKind() + { + CosmosClient client = TestCommon.CreateCosmosClient(true); + Cosmos.Database database = null; + database = await client.CreateDatabaseAsync("mydb"); + try + { + ContainerProperties containerProperties = new ContainerProperties("mycoll", new List { "/ZipCode", "/Address" }); + Container container = await database.CreateContainerAsync(containerProperties); + + //Document create. + ItemResponse[] documents = new ItemResponse[3]; + Document doc1 = new Document { Id = "document1" }; + doc1.SetValue("ZipCode", "500026"); + doc1.SetValue("Address", "Secunderabad"); + documents[0] = await container.CreateItemAsync(doc1); + + doc1 = new Document { Id = "document2" }; + doc1.SetValue("ZipCode", "15232"); + doc1.SetValue("Address", "Pittsburgh"); + documents[1] = await container.CreateItemAsync(doc1); + + doc1 = new Document { Id = "document3" }; + doc1.SetValue("ZipCode", "11790"); + doc1.SetValue("Address", "Stonybrook"); + documents[2] = await container.CreateItemAsync(doc1); + + Assert.AreEqual(3, documents.Select(document => ((Document)document).SelfLink).Distinct().Count()); + + //Negative test + { + doc1 = new Document { Id = "doc1" }; + doc1.SetValue("Zipcode", 11790); + + PartitionKeyBuilder pKValueList = new PartitionKeyBuilder(); + pKValueList.Add(doc1.GetPropertyValue("ZipCode")); + + Cosmos.PartitionKey pKeyErr = pKValueList.Build(); + ResponseMessage response = await this.Container.CreateItemStreamAsync(streamPayload: TestCommon.SerializerCore.ToStream(doc1), partitionKey: pKeyErr); + + Assert.IsNotNull(response); + Assert.IsNull(response.Content); + Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode); + } + + //Document Read. + foreach (Document document in documents) + { + Cosmos.PartitionKey pKey = new PartitionKeyBuilder() + .Add(document.GetPropertyValue("ZipCode")) + .Add(document.GetPropertyValue("Address")) + .Build(); + + Document readDocument = (await container.ReadItemAsync(document.Id, pKey)).Resource; + Assert.AreEqual(document.ToString(), readDocument.ToString()); + } + + //Document Update. + foreach (ItemResponse obj in documents) + { + Cosmos.PartitionKey pKey = new PartitionKeyBuilder() + .Add(obj.Resource.GetValue("ZipCode")) + .Add(obj.Resource.GetPropertyValue("Address")) + .Build(); + + Document document = (await container.ReadItemAsync(obj.Resource.Id, pKey)).Resource; + document.SetPropertyValue("Name", document.Id); + + Document readDocument = (await container.ReplaceItemAsync(document, document.Id, pKey)).Resource; + Assert.AreEqual(readDocument.GetValue("Name"), document.GetValue("Name")); + } + + //Document Delete. + foreach (Document document in documents) + { + Cosmos.PartitionKey pKey = new PartitionKeyBuilder() + .Add(document.GetPropertyValue("ZipCode")) + .Add(document.GetPropertyValue("Address")) + .Build(); + + Document readDocument = (await container.DeleteItemAsync(document.Id, pKey)).Resource; + try + { + readDocument = await container.ReadItemAsync(document.Id, pKey); + } + catch (CosmosException clientException) + { + Assert.AreEqual(clientException.StatusCode, HttpStatusCode.NotFound); + } + } + + } + catch (Exception) + { + Assert.Fail(); + } + finally + { + await database.DeleteAsync(); + } + + } + private async Task AutoGenerateIdPatternTest(Cosmos.PartitionKey pk, T itemWithoutId) { string autoId = Guid.NewGuid().ToString(); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs index a4cb34a94f..37860a450e 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs @@ -1786,115 +1786,6 @@ public async Task TestScriptCreateOnContainerRecreateFromDifferentPartitionKeyPa await this.TestScriptCreateOnContainerRecreateFromDifferentPartitionKeyPath(TestCommon.CreateCosmosClient(true)); } - [Ignore] //Ignoring this test until EnableSubpartitioning is set to true in BE. - [TestMethod] - public async Task VerifyDocumentCrudWithMultiHashKind() - { - CosmosClient client = TestCommon.CreateCosmosClient(true); - Cosmos.Database database = null; - database = await client.CreateDatabaseAsync("mydb"); - try - { - PartitionKeyDefinition pKDefinition = new PartitionKeyDefinition - { - Paths = new Collection { "/ZipCode", "/Address" }, - Kind = PartitionKind.MultiHash, - Version = PartitionKeyDefinitionVersion.V2 - }; - Container container = await database.CreateContainerAsync(containerProperties: new ContainerProperties { Id = "mycoll", PartitionKey = pKDefinition }); - - //Document create. - ItemResponse[] documents = new ItemResponse[3]; - Document doc1 = new Document { Id = "document1" }; - doc1.SetValue("ZipCode", "500026"); - doc1.SetValue("Address", "Secunderabad"); - documents[0] = await container.CreateItemAsync(doc1); - - doc1 = new Document { Id = "document2" }; - doc1.SetValue("ZipCode", "15232"); - doc1.SetValue("Address", "Pittsburgh"); - documents[1] = await container.CreateItemAsync(doc1); - - doc1 = new Document { Id = "document3" }; - doc1.SetValue("ZipCode", "11790"); - doc1.SetValue("Address", "Stonybrook"); - documents[2] = await container.CreateItemAsync(doc1); - - Assert.AreEqual(3, documents.Select(document => ((Document)document).SelfLink).Distinct().Count()); - - try - { - doc1 = new Document { Id = "doc1" }; - doc1.SetValue("Zipcode", 11790); - - PartitionKeyBuilder pKValueList = new PartitionKeyBuilder(); - pKValueList.Add(doc1.GetPropertyValue("ZipCode")); - - Cosmos.PartitionKey pKeyErr = pKValueList.Build(); - await container.CreateItemAsync(doc1, pKeyErr); - } - catch (ArgumentException ar) - { - Assert.AreEqual(ar.Message, RMResources.PartitionKeyMismatch); - } - - //Document Read. - foreach (Document document in documents) - { - Cosmos.PartitionKey pKey = new PartitionKeyBuilder() - .Add(document.GetPropertyValue("ZipCode")) - .Add(document.GetPropertyValue("Address")) - .Build(); - - Document readDocument = (await container.ReadItemAsync(document.Id, pKey)).Resource; - Assert.AreEqual(document.ToString(), readDocument.ToString()); - } - - //Document Update. - foreach (ItemResponse obj in documents) - { - Cosmos.PartitionKey pKey = new PartitionKeyBuilder() - .Add(obj.Resource.GetValue("ZipCode")) - .Add(obj.Resource.GetPropertyValue("Address")) - .Build(); - - Document document = (await container.ReadItemAsync(obj.Resource.Id, pKey)).Resource; - document.SetPropertyValue("Name", document.Id); - - Document readDocument = (await container.ReplaceItemAsync(document, document.Id, pKey)).Resource; - Assert.AreEqual(readDocument.GetValue("Name"), document.GetValue("Name")); - } - - //Document Delete. - foreach (Document document in documents) - { - Cosmos.PartitionKey pKey = new PartitionKeyBuilder() - .Add(document.GetPropertyValue("ZipCode")) - .Add(document.GetPropertyValue("Address")) - .Build(); - - Document readDocument = (await container.DeleteItemAsync(document.Id, pKey)).Resource; - try - { - readDocument = await container.ReadItemAsync(document.Id, pKey); - } - catch (CosmosException clientException) - { - Assert.AreEqual(clientException.StatusCode, HttpStatusCode.NotFound); - } - } - - } - catch (Exception) - { - Assert.Fail(); - } - finally - { - await database.DeleteAsync(); - } - - } internal async Task TestScriptCreateOnContainerRecreateFromDifferentPartitionKeyPath(CosmosClient client) { From 07456d94abcd8ef85ef7cca808bb4de04f974b75 Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Thu, 23 Jul 2020 18:43:52 -0700 Subject: [PATCH 20/36] Changes based on review. --- Microsoft.Azure.Cosmos/src/PartitionKey.cs | 17 ++++++++++++++++- .../src/PartitionKeyBuilder.cs | 9 +++++++-- .../Resource/Container/ContainerCore.Items.cs | 10 +++++----- .../src/Resource/Container/ContainerCore.cs | 2 +- .../src/Resource/Container/ContainerInternal.cs | 2 +- .../Resource/Settings/ContainerProperties.cs | 13 +++++++++---- .../CosmosContainerTests.cs | 2 +- .../CosmosItemTests.cs | 2 ++ .../CosmosItemUnitTests.cs | 4 ++-- 9 files changed, 44 insertions(+), 17 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/PartitionKey.cs b/Microsoft.Azure.Cosmos/src/PartitionKey.cs index 5b93a51950..7d02684b1c 100644 --- a/Microsoft.Azure.Cosmos/src/PartitionKey.cs +++ b/Microsoft.Azure.Cosmos/src/PartitionKey.cs @@ -97,12 +97,27 @@ internal PartitionKey(object value) this.IsNone = false; } + /// + /// Creates a new partition key value. + /// + /// The value to use as partition key. + #if INTERNAL || SUBPARTITIONING + internal + #else + private + #endif + PartitionKey(PartitionKeyInternal partitionKeyInternal) + { + this.InternalKey = partitionKeyInternal; + this.IsNone = false; + } + /// /// Creates a new partition key value. /// /// The value to use as partition key. /// The value to decide partitionKey is None. - internal PartitionKey(PartitionKeyInternal partitionKeyInternal, bool isNone = false) + private PartitionKey(PartitionKeyInternal partitionKeyInternal, bool isNone = false) { this.InternalKey = partitionKeyInternal; this.IsNone = isNone; diff --git a/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs b/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs index 975f775381..8779b200d4 100644 --- a/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs +++ b/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs @@ -12,7 +12,12 @@ namespace Microsoft.Azure.Cosmos /// /// Represents a partition key value list in the Azure Cosmos DB service. /// - public sealed class PartitionKeyBuilder +#if SUBPARTITIONING + public +#else + internal +#endif + sealed class PartitionKeyBuilder { private readonly IList partitionKeyValues; @@ -141,7 +146,7 @@ public PartitionKey Build() partitionKeyInternal = new Documents.PartitionKey(valueArray).InternalKey; this.isBuilt = true; - return new PartitionKey(partitionKeyInternal, false); + return new PartitionKey(partitionKeyInternal); } } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 41cd2a63fd..77aa6aaae9 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -744,10 +744,10 @@ public override async Task GetPartitionKeyValueFromStreamAsync( IJsonNavigatorNode jsonNavigatorNode = jsonNavigator.GetRootNode(); CosmosObject pathTraversal = CosmosObject.Create(jsonNavigator, jsonNavigatorNode); - IReadOnlyList tokenslist = await this.GetPartitionKeyPathTokensAsync(cancellation); + IReadOnlyList> tokenslist = await this.GetPartitionKeyPathTokensAsync(cancellation); List cosmosElementList = new List(tokenslist.Count); - foreach (string[] tokenList in tokenslist) + foreach (IReadOnlyList tokenList in tokenslist) { CosmosElement element; if (TryParseTokenListForElement(pathTraversal, tokenList, out element)) @@ -769,10 +769,10 @@ public override async Task GetPartitionKeyValueFromStreamAsync( } } - private static bool TryParseTokenListForElement(CosmosObject pathTraversal, string[] tokens, out CosmosElement result) + private static bool TryParseTokenListForElement(CosmosObject pathTraversal, IReadOnlyList tokens, out CosmosElement result) { result = null; - for (int i = 0; i < tokens.Length - 1; i++) + for (int i = 0; i < tokens.Count - 1; i++) { if (!pathTraversal.TryGetValue(tokens[i], out pathTraversal)) { @@ -780,7 +780,7 @@ private static bool TryParseTokenListForElement(CosmosObject pathTraversal, stri } } - if (!pathTraversal.TryGetValue(tokens[tokens.Length - 1], out result)) + if (!pathTraversal.TryGetValue(tokens[tokens.Count - 1], out result)) { return false; } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs index a2dd1a573a..9d504d7703 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs @@ -376,7 +376,7 @@ public override async Task GetRIDAsync(CancellationToken cancellationTok /// /// /// Returns the partition key path - public override async Task> GetPartitionKeyPathTokensAsync(CancellationToken cancellationToken = default(CancellationToken)) + public override async Task>> GetPartitionKeyPathTokensAsync(CancellationToken cancellationToken = default(CancellationToken)) { ContainerProperties containerProperties = await this.GetCachedContainerPropertiesAsync(cancellationToken); if (containerProperties == null) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs index 5371b8b543..ead4b6feb2 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs @@ -40,7 +40,7 @@ public abstract Task ReplaceThroughputIfExistsAsync( public abstract Task GetCachedContainerPropertiesAsync( CancellationToken cancellationToken = default(CancellationToken)); - public abstract Task> GetPartitionKeyPathTokensAsync( + public abstract Task>> GetPartitionKeyPathTokensAsync( CancellationToken cancellationToken = default(CancellationToken)); public abstract Task GetNonePartitionKeyValueAsync( diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs index 813f63407b..c946aa9269 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs @@ -71,7 +71,7 @@ public class ContainerProperties [JsonProperty(PropertyName = Constants.Properties.ConflictResolutionPolicy, NullValueHandling = NullValueHandling.Ignore)] private ConflictResolutionPolicy conflictResolutionInternal; - private IReadOnlyList partitionKeyPathTokens; + private IReadOnlyList> partitionKeyPathTokens; private string id; /// @@ -94,6 +94,7 @@ public ContainerProperties(string id, string partitionKeyPath) this.ValidateRequiredProperties(); } +#if SUBPARTITIONING /// /// Initializes a new instance of the class for the Azure Cosmos DB service. /// @@ -112,6 +113,7 @@ public ContainerProperties(string id, IList partitionKeyPaths) this.ValidateRequiredProperties(); } +#endif /// /// Gets or sets the /// @@ -307,6 +309,7 @@ public string PartitionKeyPath } } +#if SUBPARTITIONING /// /// JSON path used for containers partitioning /// @@ -328,6 +331,7 @@ public IList PartitionKeyPaths } } +#endif /// /// Gets or sets the time to live base time stamp property path. /// @@ -550,7 +554,7 @@ internal ContainerProperties(string id, PartitionKeyDefinition partitionKeyDefin internal bool HasPartitionKey => this.PartitionKey != null; - internal IReadOnlyList PartitionKeyPathTokens + internal IReadOnlyList> PartitionKeyPathTokens { get { @@ -574,10 +578,11 @@ internal IReadOnlyList PartitionKeyPathTokens throw new ArgumentOutOfRangeException($"Container {this.Id} is not partitioned"); } - List partitionKeyPathTokensList = new List(); + List> partitionKeyPathTokensList = new List>(); foreach (string path in this.PartitionKey?.Paths) { - partitionKeyPathTokensList.Add(path.Split(ContainerProperties.partitionKeyTokenDelimeter, StringSplitOptions.RemoveEmptyEntries)); + string[] splitPaths = path.Split(ContainerProperties.partitionKeyTokenDelimeter, StringSplitOptions.RemoveEmptyEntries); + partitionKeyPathTokensList.Add(new List(splitPaths)); } this.partitionKeyPathTokens = partitionKeyPathTokensList; 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 90107f5a0d..ffcc42e3eb 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs @@ -1122,7 +1122,7 @@ private void ValidateCreateContainerResponseContract(ContainerResponse container Assert.IsNotNull(containerSettings.PartitionKeyPath); Assert.IsNotNull(containerSettings.PartitionKeyPathTokens); - Assert.AreEqual(1, containerSettings.PartitionKeyPathTokens[0].Length); + Assert.AreEqual(1, containerSettings.PartitionKeyPathTokens[0].Count); Assert.AreEqual("id", containerSettings.PartitionKeyPathTokens[0]); ContainerInternal containerCore = containerResponse.Container as ContainerInlineCore; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs index 36ca44664b..c15ca55ac6 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs @@ -2076,6 +2076,7 @@ public async Task CustomPropertiesItemRequestOptionsTest() Assert.AreEqual(HttpStatusCode.Created, responseAstype.StatusCode); } +#if SUBPARTITIONING [Ignore] //Ignoring this test until EnableSubpartitioning is set to true in BE. [TestMethod] public async Task VerifyDocumentCrudWithMultiHashKind() @@ -2181,6 +2182,7 @@ public async Task VerifyDocumentCrudWithMultiHashKind() } +#endif private async Task AutoGenerateIdPatternTest(Cosmos.PartitionKey pk, T itemWithoutId) { string autoId = Guid.NewGuid().ToString(); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosItemUnitTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosItemUnitTests.cs index 1c42b3eda5..32d5ee673c 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosItemUnitTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosItemUnitTests.cs @@ -87,7 +87,7 @@ public async Task TestGetPartitionKeyValueFromStreamAsync() ContainerInternal container = containerMock.Object; containerMock.Setup(e => e.GetPartitionKeyPathTokensAsync(It.IsAny())) - .Returns(Task.FromResult((IReadOnlyList)new List { new string[] { "pk" } })); + .Returns(Task.FromResult((IReadOnlyList>)new List> { new List { "pk" } })); containerMock.Setup(x => x.GetPartitionKeyValueFromStreamAsync(It.IsAny(), It.IsAny())) .Returns((stream, cancellationToken) => mockContainer.GetPartitionKeyValueFromStreamAsync(stream, cancellationToken)); @@ -537,7 +537,7 @@ public async Task TestMultipleNestedPartitionKeyValueFromStreamAsync() }; mockedContainer.Setup(e => e.GetPartitionKeyPathTokensAsync(It.IsAny())) - .Returns(Task.FromResult((IReadOnlyList)new List { new string[] { "a", "b", "c" }, new string[] { "a","e","f" } })); + .Returns(Task.FromResult((IReadOnlyList>) new List> { new List { "a", "b", "c" }, new List { "a","e","f" } })); ContainerInternal containerWithMockPartitionKeyPath = mockedContainer.Object; From 4fa3f64bbabcc22c67e1b77856eff5ef3f6ffce0 Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Thu, 23 Jul 2020 18:48:36 -0700 Subject: [PATCH 21/36] Change to IReadOnlyList. --- .../src/Resource/Settings/ContainerProperties.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs index c946aa9269..db7c475e28 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs @@ -100,7 +100,7 @@ public ContainerProperties(string id, string partitionKeyPath) /// /// The Id of the resource in the Azure Cosmos service. /// The path to the partition key. Example: /location - public ContainerProperties(string id, IList partitionKeyPaths) + public ContainerProperties(string id, IReadOnlyList partitionKeyPaths) { this.Id = id; this.PartitionKey = new PartitionKeyDefinition @@ -314,7 +314,7 @@ public string PartitionKeyPath /// JSON path used for containers partitioning /// [JsonIgnore] - public IList PartitionKeyPaths + public IReadOnlyList PartitionKeyPaths { get => this.PartitionKey?.Paths != null && this.PartitionKey.Paths.Count > 0 ? this.PartitionKey?.Paths : null; set From d1b0299d62d58f0e7c9f1d56013018c0f911152a Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Thu, 23 Jul 2020 19:09:09 -0700 Subject: [PATCH 22/36] Resolving comments. --- .../src/PartitionKeyBuilder.cs | 65 ++++--------------- .../Resource/Container/ContainerCore.Items.cs | 6 +- 2 files changed, 17 insertions(+), 54 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs b/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs index 8779b200d4..b28610ee2b 100644 --- a/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs +++ b/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs @@ -21,8 +21,6 @@ sealed class PartitionKeyBuilder { private readonly IList partitionKeyValues; - private bool isBuilt = false; - /// /// Creates a new partition key value list object. /// @@ -38,11 +36,6 @@ public PartitionKeyBuilder() /// An instance of to use. public PartitionKeyBuilder Add(string val) { - if (this.isBuilt) - { - throw new InvalidOperationException("This builder instance has already been used to build a PartitionKey. Create a new instance to build another."); - } - this.partitionKeyValues.Add(val); return this; } @@ -54,11 +47,6 @@ public PartitionKeyBuilder Add(string val) /// An instance of to use. public PartitionKeyBuilder Add(double val) { - if (this.isBuilt) - { - throw new InvalidOperationException("This builder instance has already been used to build a PartitionKey. Create a new instance to build another."); - } - this.partitionKeyValues.Add(val); return this; } @@ -70,11 +58,6 @@ public PartitionKeyBuilder Add(double val) /// An instance of to use. public PartitionKeyBuilder Add(bool val) { - if (this.isBuilt) - { - throw new InvalidOperationException("This builder instance has already been used to build a PartitionKey. Create a new instance to build another."); - } - this.partitionKeyValues.Add(val); return this; } @@ -85,11 +68,6 @@ public PartitionKeyBuilder Add(bool val) /// An instance of to use. public PartitionKeyBuilder AddNullValue() { - if (this.isBuilt) - { - throw new InvalidOperationException("This builder instance has already been used to build a PartitionKey. Create a new instance to build another."); - } - this.partitionKeyValues.Add(null); return this; } @@ -100,11 +78,6 @@ public PartitionKeyBuilder AddNullValue() /// An instance of to use. public PartitionKeyBuilder AddNoneType() { - if (this.isBuilt) - { - throw new InvalidOperationException("This builder instance has already been used to build a PartitionKey. Create a new instance to build another."); - } - this.partitionKeyValues.Add(PartitionKey.None); return this; } @@ -115,39 +88,29 @@ public PartitionKeyBuilder AddNoneType() /// An instance of public PartitionKey Build() { - if (this.isBuilt) - { - throw new InvalidOperationException("This builder instance has already been used to build a PartitionKey. Create a new instance to build another."); - } - if (this.partitionKeyValues.Count == 0 || (this.partitionKeyValues.Count == 1 && PartitionKey.None.Equals(this.partitionKeyValues[0]))) { - this.isBuilt = true; return PartitionKey.None; } - else - { - PartitionKeyInternal partitionKeyInternal; - object[] valueArray = new object[this.partitionKeyValues.Count]; - for (int i = 0; i < this.partitionKeyValues.Count; i++) + PartitionKeyInternal partitionKeyInternal; + object[] valueArray = new object[this.partitionKeyValues.Count]; + for (int i = 0; i < this.partitionKeyValues.Count; i++) + { + object val = this.partitionKeyValues[i]; + if (PartitionKey.None.Equals(val)) { - object val = this.partitionKeyValues[i]; - if (PartitionKey.None.Equals(val)) - { - valueArray[i] = Undefined.Value; - } - else - { - valueArray[i] = val; - } + valueArray[i] = Undefined.Value; + } + else + { + valueArray[i] = val; } - - partitionKeyInternal = new Documents.PartitionKey(valueArray).InternalKey; - this.isBuilt = true; - return new PartitionKey(partitionKeyInternal); } + + partitionKeyInternal = new Documents.PartitionKey(valueArray).InternalKey; + return new PartitionKey(partitionKeyInternal); } } } \ 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 77aa6aaae9..8cb6588cc3 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -788,11 +788,11 @@ private static bool TryParseTokenListForElement(CosmosObject pathTraversal, IRea return true; } - private static PartitionKey CosmosElementToPartitionKeyObject(List cosmosElementList) + private static PartitionKey CosmosElementToPartitionKeyObject(IReadOnlyList cosmosElementList) { PartitionKeyBuilder partitionKeyBuilder = new PartitionKeyBuilder(); - cosmosElementList.ForEach(cosmosElement => + foreach (CosmosElement cosmosElement in cosmosElementList) { if (cosmosElement == null) { @@ -813,7 +813,7 @@ private static PartitionKey CosmosElementToPartitionKeyObject(List Date: Thu, 23 Jul 2020 23:01:21 -0700 Subject: [PATCH 23/36] Cleanup --- Microsoft.Azure.Cosmos/src/CosmosClient.cs | 2 +- Microsoft.Azure.Cosmos/src/PartitionKey.cs | 2 -- Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs | 2 +- .../src/Resource/Settings/ContainerProperties.cs | 10 ++++++---- .../CosmosItemUnitTests.cs | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/CosmosClient.cs b/Microsoft.Azure.Cosmos/src/CosmosClient.cs index c230f5f809..63957bbf5c 100644 --- a/Microsoft.Azure.Cosmos/src/CosmosClient.cs +++ b/Microsoft.Azure.Cosmos/src/CosmosClient.cs @@ -99,7 +99,7 @@ public class CosmosClient : IDisposable static CosmosClient() { - HttpConstants.Versions.CurrentVersion = HttpConstants.Versions.v2020_07_15; + HttpConstants.Versions.CurrentVersion = HttpConstants.Versions.v2018_12_31; HttpConstants.Versions.CurrentVersionUTF8 = Encoding.UTF8.GetBytes(HttpConstants.Versions.CurrentVersion); // V3 always assumes assemblies exists diff --git a/Microsoft.Azure.Cosmos/src/PartitionKey.cs b/Microsoft.Azure.Cosmos/src/PartitionKey.cs index 7d02684b1c..8907ca640a 100644 --- a/Microsoft.Azure.Cosmos/src/PartitionKey.cs +++ b/Microsoft.Azure.Cosmos/src/PartitionKey.cs @@ -4,8 +4,6 @@ namespace Microsoft.Azure.Cosmos { using System; - using System.Linq; - using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Routing; /// diff --git a/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs b/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs index b28610ee2b..5e921658b3 100644 --- a/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs +++ b/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs @@ -12,7 +12,7 @@ namespace Microsoft.Azure.Cosmos /// /// Represents a partition key value list in the Azure Cosmos DB service. /// -#if SUBPARTITIONING +#if INTERNAL || SUBPARTITIONING public #else internal diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs index db7c475e28..6e243237ce 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs @@ -94,7 +94,7 @@ public ContainerProperties(string id, string partitionKeyPath) this.ValidateRequiredProperties(); } -#if SUBPARTITIONING +#if INTERNAL || SUBPARTITIONING /// /// Initializes a new instance of the class for the Azure Cosmos DB service. /// @@ -105,7 +105,7 @@ public ContainerProperties(string id, IReadOnlyList partitionKeyPaths) this.Id = id; this.PartitionKey = new PartitionKeyDefinition { - Paths = new Collection(partitionKeyPaths), + Paths = (Collection)partitionKeyPaths, Kind = Documents.PartitionKind.MultiHash, Version = Documents.PartitionKeyDefinitionVersion.V2 }; @@ -309,7 +309,7 @@ public string PartitionKeyPath } } -#if SUBPARTITIONING +#if INTERNAL || SUBPARTITIONING /// /// JSON path used for containers partitioning /// @@ -326,7 +326,9 @@ public IReadOnlyList PartitionKeyPaths this.PartitionKey = new PartitionKeyDefinition { - Paths = new Collection(value) + Paths = (Collection)value, + Kind = Documents.PartitionKind.MultiHash, + Version = Documents.PartitionKeyDefinitionVersion.V2 }; } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosItemUnitTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosItemUnitTests.cs index 32d5ee673c..ad6ee2cfe3 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosItemUnitTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosItemUnitTests.cs @@ -452,7 +452,7 @@ public async Task TestNestedPartitionKeyValueFromStreamAsync() }; mockedContainer.Setup(e => e.GetPartitionKeyPathTokensAsync(It.IsAny())) - .Returns(Task.FromResult((IReadOnlyList)new List { new string[] { "a", "b", "c" }})); + .Returns(Task.FromResult((IReadOnlyList>)new List> {new List { "a", "b", "c" }})); ContainerInternal containerWithMockPartitionKeyPath = mockedContainer.Object; From 53214a3859a050da452eef6b91013699bda6ca64 Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Fri, 24 Jul 2020 16:15:01 -0700 Subject: [PATCH 24/36] Code review changes. --- Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs | 2 +- .../src/Resource/Container/ContainerCore.Items.cs | 4 ++-- .../Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs b/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs index 5e921658b3..b932e86ccd 100644 --- a/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs +++ b/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs @@ -19,7 +19,7 @@ namespace Microsoft.Azure.Cosmos #endif sealed class PartitionKeyBuilder { - private readonly IList partitionKeyValues; + private readonly List partitionKeyValues; /// /// Creates a new partition key value list object. diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 609885891a..bd26c46aeb 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -746,7 +746,7 @@ public override async Task GetPartitionKeyValueFromStreamAsync( foreach (IReadOnlyList tokenList in tokenslist) { CosmosElement element; - if (TryParseTokenListForElement(pathTraversal, tokenList, out element)) + if (ContainerCore.TryParseTokenListForElement(pathTraversal, tokenList, out element)) { cosmosElementList.Add(element); } @@ -756,7 +756,7 @@ public override async Task GetPartitionKeyValueFromStreamAsync( } } - return CosmosElementToPartitionKeyObject(cosmosElementList); + return ContainerCore.CosmosElementToPartitionKeyObject(cosmosElementList); } finally { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs index 37860a450e..9bdedf3812 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs @@ -1786,7 +1786,6 @@ public async Task TestScriptCreateOnContainerRecreateFromDifferentPartitionKeyPa await this.TestScriptCreateOnContainerRecreateFromDifferentPartitionKeyPath(TestCommon.CreateCosmosClient(true)); } - internal async Task TestScriptCreateOnContainerRecreateFromDifferentPartitionKeyPath(CosmosClient client) { await TestCommon.DeleteAllDatabasesAsync(); From 3dc5306d7104d5b701590568b81b4262f496a3d1 Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Mon, 27 Jul 2020 18:15:41 -0700 Subject: [PATCH 25/36] update to container properties constructor. --- .../src/Resource/Settings/ContainerProperties.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs index 6e243237ce..652eeb3d4a 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs @@ -103,9 +103,16 @@ public ContainerProperties(string id, string partitionKeyPath) public ContainerProperties(string id, IReadOnlyList partitionKeyPaths) { this.Id = id; + + Collection paths = new Collection(); + foreach (string path in partitionKeyPaths) + { + paths.Add(path); + } + this.PartitionKey = new PartitionKeyDefinition { - Paths = (Collection)partitionKeyPaths, + Paths = paths, Kind = Documents.PartitionKind.MultiHash, Version = Documents.PartitionKeyDefinitionVersion.V2 }; From f6ba9e9a05b0fa85e8be37d12d97a2e389de5b9b Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Wed, 29 Jul 2020 10:49:50 -0700 Subject: [PATCH 26/36] Apply suggestions from code review Committing suggestions. Co-authored-by: j82w --- Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs | 13 +++++++++++-- .../src/Resource/Settings/ContainerProperties.cs | 4 ++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs b/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs index b932e86ccd..47b21294a4 100644 --- a/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs +++ b/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs @@ -81,7 +81,16 @@ public PartitionKeyBuilder AddNoneType() this.partitionKeyValues.Add(PartitionKey.None); return this; } - + // Why these checks? + // These changes are being added for SDK to support multiple paths in a partition key. + // + // Currently, when a resource does not specify a value for the PartitionKey, + // we assign a temporary value `PartitionKey.None` and later discern whether + // it is a PartitionKey.Undefined or PartitionKey.Empty based on the Collection Type. + // We retain this behaviour for single path partition keys. + // + // For collections with multiple path keys, absence of a partition key values is + // always treated as a PartitionKey.Undefined. /// /// Builds a new instance of the with the specified Partition Key values. /// @@ -113,4 +122,4 @@ public PartitionKey Build() return new PartitionKey(partitionKeyInternal); } } -} \ No newline at end of file +} diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs index 652eeb3d4a..dc093ef74b 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs @@ -323,7 +323,7 @@ public string PartitionKeyPath [JsonIgnore] public IReadOnlyList PartitionKeyPaths { - get => this.PartitionKey?.Paths != null && this.PartitionKey.Paths.Count > 0 ? this.PartitionKey?.Paths : null; + get => this.PartitionKey?.Paths; set { if (value == null) @@ -626,4 +626,4 @@ internal void ValidateRequiredProperties() } } -} \ No newline at end of file +} From a874ebb811bfafa920308d0152eb4cb2f0e0778d Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Wed, 29 Jul 2020 11:43:44 -0700 Subject: [PATCH 27/36] Changes suggested in PR. --- .../src/Resource/Container/ContainerCore.Items.cs | 2 +- .../src/Resource/Settings/ContainerProperties.cs | 12 ++++++++++-- 2 files changed, 11 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 bd26c46aeb..681d806e0c 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -628,7 +628,7 @@ private async Task ExtractPartitionKeyAndProcessItemStreamAsync if (partitionKey.HasValue) { PartitionKeyDefinition pKeyDefinition = await this.GetPartitionKeyDefinitionAsync(); - if (partitionKey.HasValue && partitionKey.Value.InternalKey.Components.Count != pKeyDefinition.Paths.Count) + if (partitionKey.HasValue && partitionKey.Value != PartitionKey.None && partitionKey.Value.InternalKey.Components.Count != pKeyDefinition.Paths.Count) { throw new ArgumentException(RMResources.MissingPartitionKeyValue); } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs index dc093ef74b..9cb3bdb203 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs @@ -301,7 +301,16 @@ public GeospatialConfig GeospatialConfig [JsonIgnore] public string PartitionKeyPath { - get => this.PartitionKey?.Paths != null && this.PartitionKey.Paths.Count > 0 ? this.PartitionKey?.Paths[0] : null; + get + { + #if SUBPARTITIONING + if(this.PartitionKey?.Kind == PartitionKind.MultiHash) + { + throw new NotImplementedException($"For MultiHash collections please use `PartitionKeyPaths`"); + } + #endif + return this.PartitionKey?.Paths != null && this.PartitionKey.Paths.Count > 0 ? this.PartitionKey?.Paths[0] : null; + } set { if (string.IsNullOrEmpty(value)) @@ -624,6 +633,5 @@ internal void ValidateRequiredProperties() this.indexingPolicyInternal.IncludedPaths.Add(new IncludedPath() { Path = IndexingPolicy.DefaultPath }); } } - } } From 772e35ab84c19e78cc6896b8d892947d73127794 Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Wed, 29 Jul 2020 11:47:45 -0700 Subject: [PATCH 28/36] Better error message. --- .../src/Resource/Settings/ContainerProperties.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs index 9cb3bdb203..04233f3857 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs @@ -304,10 +304,11 @@ public string PartitionKeyPath get { #if SUBPARTITIONING - if(this.PartitionKey?.Kind == PartitionKind.MultiHash) + if(this.PartitionKey?.Kind == PartitionKind.MultiHash && this.PartitionKey?.Paths.Count > 1) { - throw new NotImplementedException($"For MultiHash collections please use `PartitionKeyPaths`"); + throw new NotImplementedException($"This MultiHash collection has more than 1 partition key path please use `PartitionKeyPaths`"); } + #endif return this.PartitionKey?.Paths != null && this.PartitionKey.Paths.Count > 0 ? this.PartitionKey?.Paths[0] : null; } From 58aa1367525a0c80627226be5d8bf369aa80fc14 Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Wed, 29 Jul 2020 11:48:59 -0700 Subject: [PATCH 29/36] Cleaner code comments. --- Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs b/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs index 47b21294a4..739fad0ad9 100644 --- a/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs +++ b/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs @@ -81,6 +81,13 @@ public PartitionKeyBuilder AddNoneType() this.partitionKeyValues.Add(PartitionKey.None); return this; } + + /// + /// Builds a new instance of the with the specified Partition Key values. + /// + /// An instance of + public PartitionKey Build() + { // Why these checks? // These changes are being added for SDK to support multiple paths in a partition key. // @@ -91,12 +98,6 @@ public PartitionKeyBuilder AddNoneType() // // For collections with multiple path keys, absence of a partition key values is // always treated as a PartitionKey.Undefined. - /// - /// Builds a new instance of the with the specified Partition Key values. - /// - /// An instance of - public PartitionKey Build() - { if (this.partitionKeyValues.Count == 0 || (this.partitionKeyValues.Count == 1 && PartitionKey.None.Equals(this.partitionKeyValues[0]))) { From 9c228624cff28770e139fb3fb822425d41223f5c Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Wed, 29 Jul 2020 12:41:43 -0700 Subject: [PATCH 30/36] throw error for no value specified case. --- Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs b/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs index 739fad0ad9..7360608c60 100644 --- a/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs +++ b/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs @@ -98,8 +98,12 @@ public PartitionKey Build() // // For collections with multiple path keys, absence of a partition key values is // always treated as a PartitionKey.Undefined. - if (this.partitionKeyValues.Count == 0 - || (this.partitionKeyValues.Count == 1 && PartitionKey.None.Equals(this.partitionKeyValues[0]))) + if (this.partitionKeyValues.Count == 0) + { + throw new ArgumentException($"No partition key value has been specifed"); + } + + if (this.partitionKeyValues.Count == 1 && PartitionKey.None.Equals(this.partitionKeyValues[0])) { return PartitionKey.None; } From 30da3e181b83725510d057d9b3b77afab9c1f14c Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Wed, 29 Jul 2020 12:57:39 -0700 Subject: [PATCH 31/36] Clean code. --- .../src/PartitionKeyBuilder.cs | 20 +++++++++---------- .../Resource/Settings/ContainerProperties.cs | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs b/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs index 7360608c60..ee23816e9b 100644 --- a/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs +++ b/Microsoft.Azure.Cosmos/src/PartitionKeyBuilder.cs @@ -81,22 +81,22 @@ public PartitionKeyBuilder AddNoneType() this.partitionKeyValues.Add(PartitionKey.None); return this; } - + /// /// Builds a new instance of the with the specified Partition Key values. /// /// An instance of public PartitionKey Build() { - // Why these checks? - // These changes are being added for SDK to support multiple paths in a partition key. - // - // Currently, when a resource does not specify a value for the PartitionKey, - // we assign a temporary value `PartitionKey.None` and later discern whether - // it is a PartitionKey.Undefined or PartitionKey.Empty based on the Collection Type. - // We retain this behaviour for single path partition keys. - // - // For collections with multiple path keys, absence of a partition key values is + // Why these checks? + // These changes are being added for SDK to support multiple paths in a partition key. + // + // Currently, when a resource does not specify a value for the PartitionKey, + // we assign a temporary value `PartitionKey.None` and later discern whether + // it is a PartitionKey.Undefined or PartitionKey.Empty based on the Collection Type. + // We retain this behaviour for single path partition keys. + // + // For collections with multiple path keys, absence of a partition key values is // always treated as a PartitionKey.Undefined. if (this.partitionKeyValues.Count == 0) { diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs index 04233f3857..b21959e904 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs @@ -304,11 +304,11 @@ public string PartitionKeyPath get { #if SUBPARTITIONING - if(this.PartitionKey?.Kind == PartitionKind.MultiHash && this.PartitionKey?.Paths.Count > 1) + if (this.PartitionKey?.Kind == PartitionKind.MultiHash && this.PartitionKey?.Paths.Count > 1) { throw new NotImplementedException($"This MultiHash collection has more than 1 partition key path please use `PartitionKeyPaths`"); } - + #endif return this.PartitionKey?.Paths != null && this.PartitionKey.Paths.Count > 0 ? this.PartitionKey?.Paths[0] : null; } From 1151d8f480b2a4565d5339c45ee7a177c5f7b1fb Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Wed, 29 Jul 2020 13:15:33 -0700 Subject: [PATCH 32/36] Emulator startup comfig add for subpartitioning. --- templates/emulator-setup.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/emulator-setup.yml b/templates/emulator-setup.yml index e29d63b2d9..d2f4563beb 100644 --- a/templates/emulator-setup.yml +++ b/templates/emulator-setup.yml @@ -11,7 +11,7 @@ steps: mkdir "$env:temp\Azure Cosmos DB Emulator" lessmsi x "$env:temp\azure-cosmosdb-emulator.msi" "$env:temp\Azure Cosmos DB Emulator\" Write-Host "Starting Comsos DB Emulator" -ForegroundColor green - Start-Process "$env:temp\Azure Cosmos DB Emulator\SourceDir\Azure Cosmos DB Emulator\CosmosDB.Emulator.exe" "/NoExplorer /NoUI /DisableRateLimiting /PartitionCount=100 /Consistency=Strong /enableRio /overrides=sqlAllowGroupByClause:true;enableDefaultIndexEncodingOptions:true;enableJsonPatch:true" -Verb RunAs + Start-Process "$env:temp\Azure Cosmos DB Emulator\SourceDir\Azure Cosmos DB Emulator\CosmosDB.Emulator.exe" "/NoExplorer /NoUI /DisableRateLimiting /PartitionCount=100 /Consistency=Strong /enableRio /overrides=sqlAllowGroupByClause:true;enableDefaultIndexEncodingOptions:true;enableJsonPatch:true;enableSubPartitioning:true" -Verb RunAs Import-Module "$env:temp\Azure Cosmos DB Emulator\SourceDir\Azure Cosmos DB Emulator\PSModules\Microsoft.Azure.CosmosDB.Emulator" Get-Item env:* | Sort-Object -Property Name for ($i=0; $i -lt 10; $i++) { From 740b2035e1e433b08ac4bf79c76ccc910ee54459 Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Thu, 30 Jul 2020 11:54:43 -0700 Subject: [PATCH 33/36] Update direct version to 3.11.4 --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 77f123be04..cc9a6db8fc 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -3,7 +3,7 @@ 3.11.0 3.11.0 - 3.11.3 + 3.11.4 1.0.0-preview4 1.0.0-preview $([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../')) From 8c8248e7ee744a99ed26403a6eb9c22a67c2285c Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Thu, 30 Jul 2020 17:23:47 -0700 Subject: [PATCH 34/36] Fix for the pipeline errors. --- Microsoft.Azure.Cosmos/src/PartitionKey.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/PartitionKey.cs b/Microsoft.Azure.Cosmos/src/PartitionKey.cs index 8907ca640a..86e0bbae58 100644 --- a/Microsoft.Azure.Cosmos/src/PartitionKey.cs +++ b/Microsoft.Azure.Cosmos/src/PartitionKey.cs @@ -99,12 +99,7 @@ internal PartitionKey(object value) /// Creates a new partition key value. /// /// The value to use as partition key. - #if INTERNAL || SUBPARTITIONING - internal - #else - private - #endif - PartitionKey(PartitionKeyInternal partitionKeyInternal) + internal PartitionKey(PartitionKeyInternal partitionKeyInternal) { this.InternalKey = partitionKeyInternal; this.IsNone = false; From cd2d448f71302d7cc94d4f0f5d1bb28b4921aa50 Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Thu, 30 Jul 2020 18:40:44 -0700 Subject: [PATCH 35/36] Test fix. --- .../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 ffcc42e3eb..e22bfb33d9 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs @@ -1123,7 +1123,7 @@ private void ValidateCreateContainerResponseContract(ContainerResponse container Assert.IsNotNull(containerSettings.PartitionKeyPath); Assert.IsNotNull(containerSettings.PartitionKeyPathTokens); Assert.AreEqual(1, containerSettings.PartitionKeyPathTokens[0].Count); - Assert.AreEqual("id", containerSettings.PartitionKeyPathTokens[0]); + Assert.AreEqual("id", containerSettings.PartitionKeyPathTokens[0][0]); ContainerInternal containerCore = containerResponse.Container as ContainerInlineCore; Assert.IsNotNull(containerCore); From 407eea71fd5e1326c873cb511e847117449833ed Mon Sep 17 00:00:00 2001 From: Naga Srinikhil Reddy Naravamakula Date: Fri, 31 Jul 2020 08:01:23 -0700 Subject: [PATCH 36/36] Override httpversion during test. --- .../CosmosItemTests.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs index c15ca55ac6..5a64872dd7 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs @@ -2076,14 +2076,15 @@ public async Task CustomPropertiesItemRequestOptionsTest() Assert.AreEqual(HttpStatusCode.Created, responseAstype.StatusCode); } -#if SUBPARTITIONING - [Ignore] //Ignoring this test until EnableSubpartitioning is set to true in BE. +#if INTERNAL || SUBPARTITIONING [TestMethod] public async Task VerifyDocumentCrudWithMultiHashKind() { + string currentVersion = HttpConstants.Versions.CurrentVersion; + HttpConstants.Versions.CurrentVersion = "2020-07-15"; CosmosClient client = TestCommon.CreateCosmosClient(true); Cosmos.Database database = null; - database = await client.CreateDatabaseAsync("mydb"); + database = await client.CreateDatabaseIfNotExistsAsync("mydb"); try { ContainerProperties containerProperties = new ContainerProperties("mycoll", new List { "/ZipCode", "/Address" }); @@ -2178,6 +2179,7 @@ public async Task VerifyDocumentCrudWithMultiHashKind() finally { await database.DeleteAsync(); + HttpConstants.Versions.CurrentVersion = currentVersion; } }