Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Partition key validation on CreateContainerIfNotExistsAsync and code documentation update #572

Merged
merged 15 commits into from
Jul 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Microsoft.Azure.Cosmos/src/ClientResources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Microsoft.Azure.Cosmos/src/ClientResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,9 @@
<data name="PathExpressionsOnly" xml:space="preserve">
<value>Only path expressions are supported for SelectMany.</value>
</data>
<data name="PartitionKeyPathConflict" xml:space="preserve">
<value>The requested partition key path '{0}' does not match existing Container '{1}' with partition key path '{2}'</value>
</data>
<data name="PKAndEpkSetTogether" xml:space="preserve">
<value>Partition key and effective partition key may not both be set.</value>
</data>
Expand Down
26 changes: 19 additions & 7 deletions Microsoft.Azure.Cosmos/src/CosmosClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -397,22 +397,34 @@ public virtual Task<DatabaseResponse> CreateDatabaseAsync(
}

/// <summary>
/// Check if a database exists, and if it doesn't, create it.
/// This will make a read operation, and if the database is not found it will do a create operation.
///
/// A database manages users, permissions and a set of containers.
/// <para>Check if a database exists, and if it doesn't, create it.
/// Only the database id is used to verify if there is an existing database. Other database properties
/// such as throughput are not validated and can be different then the passed properties.</para>
///
/// <para>A database manages users, permissions and a set of containers.
/// Each Azure Cosmos DB Database Account is able to support multiple independent named databases,
/// with the database being the logical container for data.
/// with the database being the logical container for data.</para>
///
/// Each Database consists of one or more containers, each of which in turn contain one or more
/// <para>Each Database consists of one or more containers, each of which in turn contain one or more
/// documents. Since databases are an administrative resource, the Service Master Key will be
/// required in order to access and successfully complete any action using the User APIs.
/// required in order to access and successfully complete any action using the User APIs.</para>
/// </summary>
/// <param name="id">The database id.</param>
/// <param name="throughput">(Optional) The throughput provisioned for a database in measurement of Request Units per second in the Azure Cosmos DB service.</param>
/// <param name="requestOptions">(Optional) A set of additional options that can be set.</param>
/// <param name="cancellationToken">(Optional) <see cref="CancellationToken"/> representing request cancellation.</param>
/// <returns>A <see cref="Task"/> containing a <see cref="DatabaseResponse"/> which wraps a <see cref="DatabaseProperties"/> containing the resource record.</returns>
/// <list>
/// <listheader>
/// <term>StatusCode</term><description>Common success StatusCodes for the CreateDatabaseIfNotExistsAsync operation</description>
/// </listheader>
/// <item>
/// <term>201</term><description>Created - New database is created.</description>
/// </item>
/// <item>
/// <term>200</term><description>Accepted - This means the database already exists.</description>
/// </item>
/// </list>
/// <remarks>
/// <seealso href="https://docs.microsoft.com/azure/cosmos-db/request-units"/> for details on provision throughput.
/// </remarks>
Expand Down
15 changes: 13 additions & 2 deletions Microsoft.Azure.Cosmos/src/Resource/Database/Database.cs
Original file line number Diff line number Diff line change
Expand Up @@ -352,8 +352,8 @@ public abstract Task<ContainerResponse> CreateContainerAsync(
CancellationToken cancellationToken = default(CancellationToken));

/// <summary>
/// Check if a container exists, and if it doesn't, create it.
/// This will make a read operation, and if the container is not found it will do a create operation.
/// <para>Check if a container exists, and if it doesn't, create it.
/// Only the container id is used to verify if there is an existing container. Other container properties such as throughput are not validated and can be different then the passed properties.</para>
/// </summary>
/// <param name="containerProperties">The <see cref="ContainerProperties"/> object.</param>
/// <param name="throughput">(Optional) The throughput provisioned for a container in measurement of Requests Units per second in the Azure Cosmos DB service.</param>
Expand All @@ -378,6 +378,17 @@ public abstract Task<ContainerResponse> CreateContainerAsync(
/// </item>
/// </list>
/// </exception>
/// <list>
/// <listheader>
/// <term>StatusCode</term><description>Common success StatusCodes for the CreateDatabaseIfNotExistsAsync operation</description>
/// </listheader>
/// <item>
/// <term>201</term><description>Created - New database is created.</description>
/// </item>
/// <item>
/// <term>200</term><description>Accepted - This means the database already exists.</description>
/// </item>
/// </list>
/// <example>
///
/// <code language="c#">
Expand Down
16 changes: 15 additions & 1 deletion Microsoft.Azure.Cosmos/src/Resource/Database/DatabaseCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,21 @@ public override async Task<ContainerResponse> CreateContainerIfNotExistsAsync(
ResponseMessage response = await container.ReadContainerStreamAsync(cancellationToken: cancellationToken);
if (response.StatusCode != HttpStatusCode.NotFound)
{
return await this.ClientContext.ResponseFactory.CreateContainerResponseAsync(container, Task.FromResult(response));
ContainerResponse retrivedContainerResponse = await this.ClientContext.ResponseFactory.CreateContainerResponseAsync(container, Task.FromResult(response));
if (!PartitionKeyDefinition.AreEquivalent(
retrivedContainerResponse.Resource.PartitionKey,
containerProperties.PartitionKey))
{
throw new ArgumentException(
simplynaveen20 marked this conversation as resolved.
Show resolved Hide resolved
string.Format(
ClientResources.PartitionKeyPathConflict,
containerProperties.PartitionKeyPath,
containerProperties.Id,
retrivedContainerResponse.Resource.PartitionKeyPath),
nameof(containerProperties.PartitionKey));
}

return retrivedContainerResponse;
}

this.ValidateContainerProperties(containerProperties);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,48 @@ public async Task PartitionedCreateWithPathDelete()
Assert.AreEqual(HttpStatusCode.NoContent, containerResponse.StatusCode);
}

[TestMethod]
public async Task CreateContainerIfNotExistsAsyncTest()
{
string containerName = Guid.NewGuid().ToString();
string partitionKeyPath1 = "/users";

ContainerProperties settings = new ContainerProperties(containerName, partitionKeyPath1);
ContainerResponse containerResponse = await this.cosmosDatabase.CreateContainerIfNotExistsAsync(settings);

Assert.AreEqual(HttpStatusCode.Created, containerResponse.StatusCode);
Assert.AreEqual(containerName, containerResponse.Resource.Id);
Assert.AreEqual(partitionKeyPath1, containerResponse.Resource.PartitionKey.Paths.First());

//Creating container with same partition key path
containerResponse = await this.cosmosDatabase.CreateContainerIfNotExistsAsync(settings);

Assert.AreEqual(HttpStatusCode.OK, containerResponse.StatusCode);
Assert.AreEqual(containerName, containerResponse.Resource.Id);
Assert.AreEqual(partitionKeyPath1, containerResponse.Resource.PartitionKey.Paths.First());

//Creating container with different partition key path
string partitionKeyPath2 = "/users2";
try
{
settings = new ContainerProperties(containerName, partitionKeyPath2);
containerResponse = await this.cosmosDatabase.CreateContainerIfNotExistsAsync(settings);
Assert.Fail("Should through ArgumentException on partition key path");
}
catch(ArgumentException ex)
{
Assert.AreEqual(nameof(settings.PartitionKey), ex.ParamName);
Assert.IsTrue(ex.Message.Contains(string.Format(
ClientResources.PartitionKeyPathConflict,
partitionKeyPath2,
containerName,
partitionKeyPath1)));
}

containerResponse = await containerResponse.Container.DeleteContainerAsync();
Assert.AreEqual(HttpStatusCode.NoContent, containerResponse.StatusCode);
}

[TestMethod]
public async Task StreamPartitionedCreateWithPathDelete()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace Microsoft.Azure.Cosmos.Tests
{
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.Azure.Documents;

[TestClass]
public class PartitionKeyTests
Expand Down Expand Up @@ -42,5 +43,49 @@ public void TestPartitionKeyValues()
Assert.AreEqual(testcase.Item2, new PartitionKey(testcase.Item1).ToString());
}
}

[TestMethod]
public void TestPartitionKeyDefinitionAreEquivalent()
{
//Different partition key path test
PartitionKeyDefinition definition1 = new PartitionKeyDefinition();
definition1.Paths.Add("/pk1");

PartitionKeyDefinition definition2 = new PartitionKeyDefinition();
definition2.Paths.Add("/pk2");

Assert.IsFalse(PartitionKeyDefinition.AreEquivalent(definition1, definition2));

//Different partition kind test
definition1 = new PartitionKeyDefinition();
definition1.Paths.Add("/pk1");
definition1.Kind = PartitionKind.Hash;

definition2 = new PartitionKeyDefinition();
definition2.Paths.Add("/pk1");
definition2.Kind = PartitionKind.Range;

Assert.IsFalse(PartitionKeyDefinition.AreEquivalent(definition1, definition2));

//Different partition version test
definition1 = new PartitionKeyDefinition();
definition1.Paths.Add("/pk1");
definition1.Version = PartitionKeyDefinitionVersion.V1;

definition2 = new PartitionKeyDefinition();
definition2.Paths.Add("/pk1");
definition2.Version = PartitionKeyDefinitionVersion.V2;

Assert.IsFalse(PartitionKeyDefinition.AreEquivalent(definition1, definition2));

//Same partition key path test
definition1 = new PartitionKeyDefinition();
definition1.Paths.Add("/pk1");

definition2 = new PartitionKeyDefinition();
definition2.Paths.Add("/pk1");

Assert.IsTrue(PartitionKeyDefinition.AreEquivalent(definition1, definition2));
}
}
}
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [#544](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/544) Added continuation token support for LINQ
- [#557](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/557) Added trigger options to item request options
- [#571](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/571) Added a default JSON.net serializer with optional settings
- [#572](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/572) Added partition key validation on CreateContainerIfNotExistsAsync
- [#592](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/592) Added CreateIfNotExistsAsync to container builder

### Fixed
Expand Down