From f79451e8167c4a47fe5607db0679ead61b39ed6e Mon Sep 17 00:00:00 2001 From: Christopher Currens Date: Thu, 26 May 2022 10:56:45 -0400 Subject: [PATCH 1/8] LINQ: preserve DateTime.Kind when passing to custom JsonConverter --- Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs b/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs index 3bbf936bce..ba64ee8fd5 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs @@ -551,7 +551,7 @@ private static SqlScalarExpression ApplyCustomConverters(Expression left, SqlLit else if (memberType == typeof(DateTime)) { SqlStringLiteral serializedDateTime = (SqlStringLiteral)right.Literal; - value = DateTime.Parse(serializedDateTime.Value); + value = DateTime.Parse(serializedDateTime.Value, null, DateTimeStyles.RoundtripKind); } if (value != default(object)) From f9c62b24737fa428b7ce58558715768c61be56a8 Mon Sep 17 00:00:00 2001 From: Christopher Currens Date: Thu, 26 May 2022 13:38:30 -0400 Subject: [PATCH 2/8] Add named parameter --- Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs b/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs index ba64ee8fd5..4e3174895f 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs @@ -551,7 +551,7 @@ private static SqlScalarExpression ApplyCustomConverters(Expression left, SqlLit else if (memberType == typeof(DateTime)) { SqlStringLiteral serializedDateTime = (SqlStringLiteral)right.Literal; - value = DateTime.Parse(serializedDateTime.Value, null, DateTimeStyles.RoundtripKind); + value = DateTime.Parse(serializedDateTime.Value, provider: null, DateTimeStyles.RoundtripKind); } if (value != default(object)) From 209d5a8a75773a83c4a48db5f73aaa388b4a381d Mon Sep 17 00:00:00 2001 From: Christopher Currens Date: Thu, 26 May 2022 14:09:38 -0400 Subject: [PATCH 3/8] Add unit test --- .../Linq/CosmosLinqJsonConverterTests.cs | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs new file mode 100644 index 0000000000..10e0a33eef --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs @@ -0,0 +1,64 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Linq +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Linq.Expressions; + using System.Threading.Tasks; + using Castle.DynamicProxy.Generators.Emitters.SimpleAST; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Newtonsoft.Json; + using Newtonsoft.Json.Converters; + using SqlObjects; + + [TestClass] + public class CosmosLinqJsonConverterTests + { + [TestMethod] + public void DateTimeKindIsPreservedTest() + { + // NOTE: This test does not actually use the emulator, just a convenient way to get access + // to a container to create a Linq queryable + const string account = "https://localhost:8081"; + const string key = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="; + using CosmosClient client = new CosmosClient(account, key); + Container container = client.GetContainer("NotEven", "MakingARequest"); + + // Should work for UTC time + DateTime utcDate = new DateTime(2022, 5, 26, 0, 0, 0, DateTimeKind.Utc); + IQueryable query = container.GetItemLinqQueryable().Where(a => a.StartDate <= utcDate); + Assert.IsTrue(query.ToString().Contains("2022-05-26")); + + // Should work for local time + DateTime localDate = new DateTime(2022, 5, 26, 0, 0, 0, DateTimeKind.Local); + query = container.GetItemLinqQueryable().Where(a => a.StartDate <= localDate); + Assert.IsTrue(query.ToString().Contains("2022-05-26")); + } + + class TestDocument + { + [JsonConverter(typeof(DateJsonConverter))] + public DateTime StartDate { get; set; } + } + + class DateJsonConverter : IsoDateTimeConverter + { + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value is DateTime dateTime) + { + writer.WriteValue(dateTime.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)); + } + else + { + base.WriteJson(writer, value, serializer); + } + } + } + } +} From 4e6a4f76a697db7e9902c75092e9c1f4d345f22c Mon Sep 17 00:00:00 2001 From: Christopher Currens Date: Fri, 27 May 2022 08:03:48 -0400 Subject: [PATCH 4/8] Update test --- .../Linq/CosmosLinqJsonConverterTests.cs | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs index 10e0a33eef..3a49dd4461 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs @@ -5,16 +5,19 @@ namespace Microsoft.Azure.Cosmos.Linq { using System; + using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Linq.Expressions; + using System.Threading; using System.Threading.Tasks; using Castle.DynamicProxy.Generators.Emitters.SimpleAST; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using SqlObjects; + using Expression = System.Linq.Expressions.Expression; [TestClass] public class CosmosLinqJsonConverterTests @@ -22,22 +25,17 @@ public class CosmosLinqJsonConverterTests [TestMethod] public void DateTimeKindIsPreservedTest() { - // NOTE: This test does not actually use the emulator, just a convenient way to get access - // to a container to create a Linq queryable - const string account = "https://localhost:8081"; - const string key = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="; - using CosmosClient client = new CosmosClient(account, key); - Container container = client.GetContainer("NotEven", "MakingARequest"); - // Should work for UTC time DateTime utcDate = new DateTime(2022, 5, 26, 0, 0, 0, DateTimeKind.Utc); - IQueryable query = container.GetItemLinqQueryable().Where(a => a.StartDate <= utcDate); - Assert.IsTrue(query.ToString().Contains("2022-05-26")); + Expression> expr = a => a.StartDate <= utcDate; + string sql = SqlTranslator.TranslateExpression(expr.Body); + Assert.AreEqual("(a[\"StartDate\"] <= \"2022-05-26\")", sql); - // Should work for local time + //// Should work for local time DateTime localDate = new DateTime(2022, 5, 26, 0, 0, 0, DateTimeKind.Local); - query = container.GetItemLinqQueryable().Where(a => a.StartDate <= localDate); - Assert.IsTrue(query.ToString().Contains("2022-05-26")); + expr = a => a.StartDate <= localDate; + sql = SqlTranslator.TranslateExpression(expr.Body); + Assert.AreEqual("(a[\"StartDate\"] <= \"2022-05-26\")", sql); } class TestDocument From a8a4e024abe6b82c5454031253aa0ad33fc7f73a Mon Sep 17 00:00:00 2001 From: Christopher Currens Date: Fri, 27 May 2022 08:06:14 -0400 Subject: [PATCH 5/8] Remove unused namespaces --- .../Linq/CosmosLinqJsonConverterTests.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs index 3a49dd4461..1f2ce09772 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs @@ -5,19 +5,11 @@ namespace Microsoft.Azure.Cosmos.Linq { using System; - using System.Collections; - using System.Collections.Generic; using System.Globalization; - using System.Linq; using System.Linq.Expressions; - using System.Threading; - using System.Threading.Tasks; - using Castle.DynamicProxy.Generators.Emitters.SimpleAST; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; using Newtonsoft.Json.Converters; - using SqlObjects; - using Expression = System.Linq.Expressions.Expression; [TestClass] public class CosmosLinqJsonConverterTests From d87396b6fff455d5e0217145ece500da28b6ca75 Mon Sep 17 00:00:00 2001 From: Christopher Currens Date: Fri, 3 Jun 2022 09:42:13 -0400 Subject: [PATCH 6/8] Fix nit --- .../Linq/CosmosLinqJsonConverterTests.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs index 1f2ce09772..deba94b4f4 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs @@ -7,6 +7,7 @@ namespace Microsoft.Azure.Cosmos.Linq using System; using System.Globalization; using System.Linq.Expressions; + using Fluent; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; using Newtonsoft.Json.Converters; @@ -23,13 +24,19 @@ public void DateTimeKindIsPreservedTest() string sql = SqlTranslator.TranslateExpression(expr.Body); Assert.AreEqual("(a[\"StartDate\"] <= \"2022-05-26\")", sql); - //// Should work for local time + // Should work for local time DateTime localDate = new DateTime(2022, 5, 26, 0, 0, 0, DateTimeKind.Local); expr = a => a.StartDate <= localDate; sql = SqlTranslator.TranslateExpression(expr.Body); Assert.AreEqual("(a[\"StartDate\"] <= \"2022-05-26\")", sql); } + [TestMethod] + public void DateTimeKindIsPreserved_GetItemLinqQueryable() + { + var container = + } + class TestDocument { [JsonConverter(typeof(DateJsonConverter))] From 5044acd6e5438bae8217336980ede50537f31aa4 Mon Sep 17 00:00:00 2001 From: Christopher Currens Date: Fri, 3 Jun 2022 10:26:20 -0400 Subject: [PATCH 7/8] Add emulator test --- ...sts.TestDateTimeJsonConverterTimezones.xml | 26 +++++++++++++++++++ .../LinqTranslationBaselineTests.cs | 24 +++++++++++++++++ ...icrosoft.Azure.Cosmos.EmulatorTests.csproj | 4 +++ .../Linq/CosmosLinqJsonConverterTests.cs | 7 ----- 4 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestDateTimeJsonConverterTimezones.xml diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestDateTimeJsonConverterTimezones.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestDateTimeJsonConverterTimezones.xml new file mode 100644 index 0000000000..9a60a6cf44 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestDateTimeJsonConverterTimezones.xml @@ -0,0 +1,26 @@ + + + + + (doc.IsoTime == new DateTime(2016, 9, 13, 0, 0, 0, Local)))]]> + + + + + + + + + (doc.IsoTime == new DateTime(2016, 9, 13, 0, 0, 0, Utc)))]]> + + + + + + \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationBaselineTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationBaselineTests.cs index a3b62a1ff1..6b6af10755 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationBaselineTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationBaselineTests.cs @@ -350,6 +350,30 @@ public void TestDateTimeJsonConverter() this.ExecuteTestSuite(inputs); } + [TestMethod] + public void TestDateTimeJsonConverterTimezones() + { + const int Records = 10; + DateTime midDateTime = new (2016, 9, 13, 0, 0, 0); + Func createDataObj = (random) => + { + DataObject obj = new() { + IsoTime = LinqTestsCommon.RandomDateTime(random, midDateTime), + Id = Guid.NewGuid().ToString(), + Pk = "Test" + }; + return obj; + }; + Func> getQuery = LinqTestsCommon.GenerateTestCosmosData(createDataObj, Records, testContainer); + + List inputs = new() + { + new LinqTestInput("IsoDateTimeConverter LocalTime = filter", b => getQuery(b).Where(doc => doc.IsoTime == new DateTime(2016, 9, 13, 0, 0, 0, DateTimeKind.Local))), + new LinqTestInput("IsoDateTimeConverter UniversalTime = filter", b => getQuery(b).Where(doc => doc.IsoTime == new DateTime(2016, 9, 13, 0, 0, 0, DateTimeKind.Utc))), + }; + this.ExecuteTestSuite(inputs); + } + [TestMethod] public void TestNullableFields() { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj index 5e69fbb1e0..24f2bc44bc 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj @@ -34,6 +34,7 @@ + @@ -189,6 +190,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs index deba94b4f4..e4f90f1957 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs @@ -7,7 +7,6 @@ namespace Microsoft.Azure.Cosmos.Linq using System; using System.Globalization; using System.Linq.Expressions; - using Fluent; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; using Newtonsoft.Json.Converters; @@ -31,12 +30,6 @@ public void DateTimeKindIsPreservedTest() Assert.AreEqual("(a[\"StartDate\"] <= \"2022-05-26\")", sql); } - [TestMethod] - public void DateTimeKindIsPreserved_GetItemLinqQueryable() - { - var container = - } - class TestDocument { [JsonConverter(typeof(DateJsonConverter))] From cc2f590ef3077fb833d725e100d405d15d8c462a Mon Sep 17 00:00:00 2001 From: Christopher Currens Date: Fri, 3 Jun 2022 13:47:01 -0400 Subject: [PATCH 8/8] Update test to not use timezones --- ...sts.TestDateTimeJsonConverterTimezones.xml | 8 +++--- .../LinqTranslationBaselineTests.cs | 25 ++++++++++++++++--- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestDateTimeJsonConverterTimezones.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestDateTimeJsonConverterTimezones.xml index 9a60a6cf44..6c3ceb9716 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestDateTimeJsonConverterTimezones.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestDateTimeJsonConverterTimezones.xml @@ -2,25 +2,25 @@ - (doc.IsoTime == new DateTime(2016, 9, 13, 0, 0, 0, Local)))]]> + (doc.IsoDateOnly == new DateTime(2016, 9, 13, 0, 0, 0, Local)))]]> +WHERE (root["IsoDateOnly"] = "2016-09-13")]]> - (doc.IsoTime == new DateTime(2016, 9, 13, 0, 0, 0, Utc)))]]> + (doc.IsoDateOnly == new DateTime(2016, 9, 13, 0, 0, 0, Utc)))]]> +WHERE (root["IsoDateOnly"] = "2016-09-13")]]> \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationBaselineTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationBaselineTests.cs index 6b6af10755..d25f213868 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationBaselineTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationBaselineTests.cs @@ -11,6 +11,7 @@ namespace Microsoft.Azure.Cosmos.Services.Management.Tests.LinqProviderTests using Newtonsoft.Json; using System; using System.Collections.Generic; + using System.Globalization; using System.Linq; using Newtonsoft.Json.Converters; using BaselineTest; @@ -134,6 +135,9 @@ internal class DataObject : LinqTestObject [JsonConverter(typeof(IsoDateTimeConverter))] public DateTime IsoTime; + [JsonConverter(typeof(DateJsonConverter))] + public DateTime IsoDateOnly; + // This field should serialize as ISO Date // as this is the default DateTimeConverter // used by Newtonsoft @@ -145,6 +149,21 @@ internal class DataObject : LinqTestObject public string Pk; } + class DateJsonConverter : IsoDateTimeConverter + { + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value is DateTime dateTime) + { + writer.WriteValue(dateTime.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)); + } + else + { + base.WriteJson(writer, value, serializer); + } + } + } + internal class AmbientContextObject { public double FieldAccess; @@ -358,7 +377,7 @@ public void TestDateTimeJsonConverterTimezones() Func createDataObj = (random) => { DataObject obj = new() { - IsoTime = LinqTestsCommon.RandomDateTime(random, midDateTime), + IsoDateOnly = LinqTestsCommon.RandomDateTime(random, midDateTime), Id = Guid.NewGuid().ToString(), Pk = "Test" }; @@ -368,8 +387,8 @@ public void TestDateTimeJsonConverterTimezones() List inputs = new() { - new LinqTestInput("IsoDateTimeConverter LocalTime = filter", b => getQuery(b).Where(doc => doc.IsoTime == new DateTime(2016, 9, 13, 0, 0, 0, DateTimeKind.Local))), - new LinqTestInput("IsoDateTimeConverter UniversalTime = filter", b => getQuery(b).Where(doc => doc.IsoTime == new DateTime(2016, 9, 13, 0, 0, 0, DateTimeKind.Utc))), + new LinqTestInput("IsoDateTimeConverter LocalTime = filter", b => getQuery(b).Where(doc => doc.IsoDateOnly == new DateTime(2016, 9, 13, 0, 0, 0, DateTimeKind.Local))), + new LinqTestInput("IsoDateTimeConverter UniversalTime = filter", b => getQuery(b).Where(doc => doc.IsoDateOnly == new DateTime(2016, 9, 13, 0, 0, 0, DateTimeKind.Utc))), }; this.ExecuteTestSuite(inputs); }