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

Fixed SpatialPath serialization and compatibility with older index versions #614

Merged
merged 9 commits into from
Aug 1, 2019
Merged
Show file tree
Hide file tree
Changes from 2 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
90 changes: 90 additions & 0 deletions Microsoft.Azure.Cosmos/src/CosmosIndexJsonConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------

namespace Microsoft.Azure.Cosmos
{
using System;
using System.Globalization;
using Microsoft.Azure.Documents;
j82w marked this conversation as resolved.
Show resolved Hide resolved
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

internal sealed class CosmosIndexJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Index).IsAssignableFrom(objectType);
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (objectType != typeof(Index))
{
return null;
}

JToken indexToken = JToken.Load(reader);

if (indexToken.Type == JTokenType.Null)
{
return null;
}

if (indexToken.Type != JTokenType.Object)
{
throw new JsonSerializationException(
string.Format(CultureInfo.CurrentCulture, RMResources.InvalidIndexSpecFormat));
}

JToken indexKindToken = indexToken[Constants.Properties.IndexKind];
if (indexKindToken == null || indexKindToken.Type != JTokenType.String)
{
throw new JsonSerializationException(
string.Format(CultureInfo.CurrentCulture, RMResources.InvalidIndexSpecFormat));
}

IndexKind indexKind = IndexKind.Hash;
if (Enum.TryParse(indexKindToken.Value<string>(), out indexKind))
{
object index = null;
switch (indexKind)
{
case IndexKind.Hash:
index = new HashIndex();
break;
case IndexKind.Range:
index = new RangeIndex();
break;
case IndexKind.Spatial:
index = new SpatialIndex();
break;
default:
throw new JsonSerializationException(
string.Format(CultureInfo.CurrentCulture, RMResources.InvalidIndexKindValue, indexKind));
}

serializer.Populate(indexToken.CreateReader(), index);
return index;
}
else
{
throw new JsonSerializationException(
string.Format(CultureInfo.CurrentCulture, RMResources.InvalidIndexKindValue, indexKindToken.Value<string>()));
}
}

public override bool CanWrite
{
get
{
return false;
}
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}
2 changes: 1 addition & 1 deletion Microsoft.Azure.Cosmos/src/Resource/Settings/Index.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace Microsoft.Azure.Cosmos
/// <summary>
/// Base class for IndexingPolicy Indexes in the Azure Cosmos DB service, you should use a concrete Index like HashIndex or RangeIndex.
/// </summary>
[JsonConverter(typeof(IndexJsonConverter))]
[JsonConverter(typeof(CosmosIndexJsonConverter))]
internal abstract class Index
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests
using System.Net;
using System.Linq;
using Newtonsoft.Json.Linq;
using System.Collections.ObjectModel;
using System.IO;

// Similar tests to CosmosContainerTests but with Fluent syntax
[TestClass]
Expand All @@ -33,21 +35,74 @@ public async Task Cleanup()
[TestMethod]
public async Task ContainerContractTest()
{
ContainerResponse response =
await this.database.DefineContainer(new Guid().ToString(), "/id")
.CreateAsync();
ContainerProperties containerProperties = new ContainerProperties(Guid.NewGuid().ToString(), "/users")
{
IndexingPolicy = new IndexingPolicy()
{
Automatic = true,
IndexingMode = IndexingMode.Consistent,
IncludedPaths = new Collection<IncludedPath>()
{
new IncludedPath()
{
Path = "/*"
j82w marked this conversation as resolved.
Show resolved Hide resolved
}
},
ExcludedPaths = new Collection<ExcludedPath>()
{
new ExcludedPath()
{
Path = "/test/*"
}
},
CompositeIndexes = new Collection<Collection<CompositePath>>()
{
new Collection<CompositePath>()
{
new CompositePath()
{
Path = "/address/city",
Order = CompositePathSortOrder.Ascending
},
new CompositePath()
{
Path = "/address/zipcode",
Order = CompositePathSortOrder.Descending
}
}
}
}
};

var serializer = new CosmosJsonDotNetSerializer();
Stream stream = serializer.ToStream(containerProperties);
ContainerProperties deserialziedTest = serializer.FromStream<ContainerProperties>(stream);

ContainerResponse response = await this.database.CreateContainerAsync(containerProperties);
Assert.IsNotNull(response);
Assert.IsTrue(response.RequestCharge > 0);
Assert.IsNotNull(response.Headers);
Assert.IsNotNull(response.Headers.ActivityId);

ContainerProperties containerSettings = response.Resource;
Assert.IsNotNull(containerSettings.Id);
Assert.IsNotNull(containerSettings.ResourceId);
Assert.IsNotNull(containerSettings.ETag);
Assert.IsTrue(containerSettings.LastModified.HasValue);
ContainerProperties responseProperties = response.Resource;
Assert.IsNotNull(responseProperties.Id);
Assert.IsNotNull(responseProperties.ResourceId);
Assert.IsNotNull(responseProperties.ETag);
Assert.IsTrue(responseProperties.LastModified.HasValue);

Assert.IsTrue(responseProperties.LastModified.Value > new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), responseProperties.LastModified.Value.ToString());

Assert.AreEqual(1, responseProperties.IndexingPolicy.IncludedPaths.Count);
IncludedPath includedPath = responseProperties.IndexingPolicy.IncludedPaths.First();
Assert.AreEqual("/*", includedPath.Path);

Assert.AreEqual("/test/*", responseProperties.IndexingPolicy.ExcludedPaths.First().Path);

Assert.IsTrue(containerSettings.LastModified.Value > new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), containerSettings.LastModified.Value.ToString());
Assert.AreEqual(1, responseProperties.IndexingPolicy.CompositeIndexes.Count);
Assert.AreEqual(2, responseProperties.IndexingPolicy.CompositeIndexes.First().Count);
CompositePath compositePath = responseProperties.IndexingPolicy.CompositeIndexes.First().First();
Assert.AreEqual("/address/city", compositePath.Path);
Assert.AreEqual(CompositePathSortOrder.Ascending, compositePath.Order);
}

[TestMethod]
Expand Down Expand Up @@ -177,6 +232,7 @@ public async Task WithIndexingPolicy()
string containerName = Guid.NewGuid().ToString();
string partitionKeyPath = "/users";


j82w marked this conversation as resolved.
Show resolved Hide resolved
ContainerResponse containerResponse =
await this.database.DefineContainer(containerName, partitionKeyPath)
.WithIndexingPolicy()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@

namespace Microsoft.Azure.Cosmos.Tests
{
using Microsoft.Azure.Cosmos.Linq;
using Microsoft.Azure.Cosmos.Scripts;
using Microsoft.Azure.Documents;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos.Scripts;
using Microsoft.Azure.Documents;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;

[TestClass]
public class SettingsContractTests
Expand Down Expand Up @@ -87,7 +88,7 @@ public void DatabaseStreamDeserialzieTest()
+ "\",\"_etag\":\"" + etag
+ "\",\"_colls\":\"colls\\/\",\"_users\":\"users\\/\",\"_ts\":" + ts + "}";

DatabaseProperties deserializedPayload =
DatabaseProperties deserializedPayload =
JsonConvert.DeserializeObject<DatabaseProperties>(testPyaload);

Assert.IsTrue(deserializedPayload.LastModified.HasValue);
Expand Down Expand Up @@ -348,11 +349,11 @@ public void ContainerSettingsDefaults()
string id = Guid.NewGuid().ToString();
string pkPath = "/partitionKey";

SettingsContractTests.TypeAccessorGuard(typeof(ContainerProperties),
"Id",
"UniqueKeyPolicy",
"DefaultTimeToLive",
"IndexingPolicy",
SettingsContractTests.TypeAccessorGuard(typeof(ContainerProperties),
"Id",
"UniqueKeyPolicy",
"DefaultTimeToLive",
"IndexingPolicy",
"TimeToLivePropertyPath",
"PartitionKeyPath",
"PartitionKeyDefinitionVersion",
Expand Down Expand Up @@ -387,6 +388,33 @@ public void ContainerSettingsDefaults()
Assert.IsNotNull(uk.Paths);
}

[TestMethod]
public async Task ContainerSettingsIndexTest()
{
string containerJsonString = "{\"indexingPolicy\":{\"automatic\":true,\"indexingMode\":\"Consistent\",\"includedPaths\":[{\"path\":\"/*\",\"indexes\":[{\"dataType\":\"Number\",\"precision\":-1,\"kind\":\"Range\"},{\"dataType\":\"String\",\"precision\":-1,\"kind\":\"Range\"}]}],\"excludedPaths\":[{\"path\":\"/\\\"_etag\\\"/?\"}],\"compositeIndexes\":[],\"spatialIndexes\":[]},\"id\":\"MigrationTest\",\"partitionKey\":{\"paths\":[\"/id\"],\"kind\":\"Hash\"}}";

CosmosJsonDotNetSerializer cosmosSerializer = new CosmosJsonDotNetSerializer();
ContainerProperties containerProperties = null;
using (MemoryStream memory = new MemoryStream(Encoding.UTF8.GetBytes(containerJsonString)))
{
containerProperties = cosmosSerializer.FromStream<ContainerProperties>(memory);
}

Assert.IsNotNull(containerProperties);
Assert.AreEqual("MigrationTest", containerProperties.Id);

string containerJsonAfterConversion = null;
using (Stream stream = cosmosSerializer.ToStream<ContainerProperties>(containerProperties))
{
using(StreamReader sr = new StreamReader(stream))
{
containerJsonAfterConversion = await sr.ReadToEndAsync();
}
}

Assert.AreEqual(containerJsonString, containerJsonAfterConversion);
}

[TestMethod]
public void CosmosAccountSettingsSerializationTest()
{
Expand All @@ -395,7 +423,7 @@ public void CosmosAccountSettingsSerializationTest()
cosmosAccountSettings.EnableMultipleWriteLocations = true;
cosmosAccountSettings.ResourceId = "/uri";
cosmosAccountSettings.ETag = "etag";
cosmosAccountSettings.WriteLocationsInternal = new Collection<AccountRegion>() { new AccountRegion() { Name="region1", Endpoint = "endpoint1" } };
cosmosAccountSettings.WriteLocationsInternal = new Collection<AccountRegion>() { new AccountRegion() { Name = "region1", Endpoint = "endpoint1" } };
cosmosAccountSettings.ReadLocationsInternal = new Collection<AccountRegion>() { new AccountRegion() { Name = "region2", Endpoint = "endpoint2" } };
cosmosAccountSettings.AddressesLink = "link";
cosmosAccountSettings.Consistency = new AccountConsistency() { DefaultConsistencyLevel = Cosmos.ConsistencyLevel.BoundedStaleness };
Expand Down Expand Up @@ -486,7 +514,7 @@ private static T CosmosDeserialize<T>(string payload)
}
}

private static T DirectDeSerialize<T>(string payload) where T: JsonSerializable, new()
private static T DirectDeSerialize<T>(string payload) where T : JsonSerializable, new()
{
using (MemoryStream ms = new MemoryStream())
{
Expand All @@ -510,7 +538,7 @@ private static string CosmosSerialize(object input)
}
}

private static string DirectSerialize<T>(T input) where T: JsonSerializable
private static string DirectSerialize<T>(T input) where T : JsonSerializable
{
using (MemoryStream ms = new MemoryStream())
{
Expand All @@ -527,7 +555,7 @@ private static string DirectSerialize<T>(T input) where T: JsonSerializable
private static void TypeAccessorGuard(Type input, params string[] publicSettable)
{
// All properties are public readable only by-default
PropertyInfo[] allProperties = input.GetProperties(BindingFlags.Instance|BindingFlags.Public);
PropertyInfo[] allProperties = input.GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (PropertyInfo pInfo in allProperties)
{
MethodInfo[] accessors = pInfo.GetAccessors();
Expand All @@ -550,7 +578,7 @@ private static void TypeAccessorGuard(Type input, params string[] publicSettable
}
}

private void AssertEnums<TFirstEnum,TSecondEnum>() where TFirstEnum : struct, IConvertible where TSecondEnum : struct, IConvertible
private void AssertEnums<TFirstEnum, TSecondEnum>() where TFirstEnum : struct, IConvertible where TSecondEnum : struct, IConvertible
{
string[] allCosmosEntries = Enum.GetNames(typeof(TFirstEnum));
string[] allDocumentsEntries = Enum.GetNames(typeof(TSecondEnum));
Expand Down
6 changes: 5 additions & 1 deletion changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

### Fixed

- [#614](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/614) Fixed Index serialization bug

## [3.1.0](https://www.nuget.org/packages/Microsoft.Azure.Cosmos/3.1.0) - 2019-07-26

### Added
Expand All @@ -14,7 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [#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
- [#581](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/581) Adding LINQ to QueryDefinition API
- [#581](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/581) Added LINQ to QueryDefinition API
- [#592](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/592) Added CreateIfNotExistsAsync to container builder
- [#597](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/597) Added continuation token property to ResponseMessage
- [#604](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/604) Added LINQ ToStreamIterator extension method
Expand Down