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 11 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
11 changes: 7 additions & 4 deletions Microsoft.Azure.Cosmos/src/CosmosClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -397,12 +397,15 @@ 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.
/// <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>
///
/// Status code 201: New database is created.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    /// <returns>
    /// A <see cref="Task"/> containing a <see cref="DatabaseResponse"/> which wraps a <see cref="DatabaseProperties"/> containing the read resource record.
    /// </returns>
    /// <exception cref="CosmosException">This exception can encapsulate many different types of errors. To determine the specific error always look at the StatusCode property. Some common codes you may get when creating a Document are:
    /// <list>
    ///     <listheader>
    ///         <term>StatusCode</term><description>Reason for exception</description>
    ///     </listheader>
    ///     <item>
    ///         <term>404</term><description>NotFound - This means the resource you tried to read did not exist.</description>
    ///     </item>
    ///     <item>
    ///         <term>429</term><description>TooManyRequests - This means you have exceeded the number of request units per second.</description>
    ///     </item>
    /// </list>

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StatusCode representation consistency with other places

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

404 is not applicable in case of createifnotexist

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pushed a new iteration using the list format for the status codes.

/// Status code 200: Database already exist.
///
/// A database manages users, permissions and a set of containers.
/// <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
/// documents. Since databases are an administrative resource, the Service Master Key will be
Expand Down
7 changes: 5 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,11 @@ 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>
///
/// Status code 201: New container is created.
/// Status code 200: Container already exist.
/// </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 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 @@ -152,6 +152,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

### Fixed

Expand Down