From 5412f334218e77139a17462385d8151182b06fa6 Mon Sep 17 00:00:00 2001 From: rstam Date: Wed, 9 Aug 2023 11:44:10 -0700 Subject: [PATCH] CSHARP-4743: Add support for DateTime.Date and DateTime.TimeOfDay. --- ...essionToAggregationExpressionTranslator.cs | 58 +++++--- .../Jira/CSharp4743Tests.cs | 133 ++++++++++++++++++ 2 files changed, 169 insertions(+), 22 deletions(-) create mode 100644 tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp4743Tests.cs diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MemberExpressionToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MemberExpressionToAggregationExpressionTranslator.cs index c5aca5bb8fe..9bf0390dd1f 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MemberExpressionToAggregationExpressionTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MemberExpressionToAggregationExpressionTranslator.cs @@ -20,6 +20,7 @@ using System.Reflection; using MongoDB.Bson; using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Options; using MongoDB.Bson.Serialization.Serializers; using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions; using MongoDB.Driver.Linq.Linq3Implementation.Misc; @@ -128,29 +129,42 @@ private static bool TryTranslateDateTimeProperty(MemberExpression expression, Ag AstExpression ast; IBsonSerializer serializer; - if (propertyInfo.Name == "DayOfWeek") + switch (propertyInfo.Name) { - ast = AstExpression.Subtract(AstExpression.DatePart(AstDatePart.DayOfWeek, container.Ast), 1); - serializer = new EnumSerializer(BsonType.Int32); - } - else - { - AstDatePart datePart; - switch (propertyInfo.Name) - { - case "Day": datePart = AstDatePart.DayOfMonth; break; - case "DayOfWeek": datePart = AstDatePart.DayOfWeek; break; - case "DayOfYear": datePart = AstDatePart.DayOfYear; break; - case "Hour": datePart = AstDatePart.Hour; break; - case "Millisecond": datePart = AstDatePart.Millisecond; break; - case "Minute": datePart = AstDatePart.Minute; break; - case "Month": datePart = AstDatePart.Month; break; - case "Second": datePart = AstDatePart.Second; break; - case "Year": datePart = AstDatePart.Year; break; - default: return false; - } - ast = AstExpression.DatePart(datePart, container.Ast); - serializer = new Int32Serializer(); + case "Date": + ast = AstExpression.DateTrunc(container.Ast, "day"); + serializer = container.Serializer; + break; + + case "DayOfWeek": + ast = AstExpression.Subtract(AstExpression.DatePart(AstDatePart.DayOfWeek, container.Ast), 1); + serializer = new EnumSerializer(BsonType.Int32); + break; + + case "TimeOfDay": + var endDate = container.Ast; + var startDate = AstExpression.DateTrunc(container.Ast, "day"); + ast = AstExpression.DateDiff(startDate, endDate, "millisecond"); + serializer = new TimeSpanSerializer(BsonType.Int64, TimeSpanUnits.Milliseconds); + break; + + default: + var datePart = propertyInfo.Name switch + { + "Day" => AstDatePart.DayOfMonth, + "DayOfWeek" => AstDatePart.DayOfWeek, + "DayOfYear" => AstDatePart.DayOfYear, + "Hour" => AstDatePart.Hour, + "Millisecond" => AstDatePart.Millisecond, + "Minute" => AstDatePart.Minute, + "Month" => AstDatePart.Month, + "Second" => AstDatePart.Second, + "Year" => AstDatePart.Year, + _ => throw new ExpressionNotSupportedException(expression) + }; + ast = AstExpression.DatePart(datePart, container.Ast); + serializer = new Int32Serializer(); + break; } result = new AggregationExpression(expression, ast, serializer); diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp4743Tests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp4743Tests.cs new file mode 100644 index 00000000000..84dd2a406c8 --- /dev/null +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp4743Tests.cs @@ -0,0 +1,133 @@ +/* Copyright 2010-present MongoDB Inc. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +using System; +using FluentAssertions; +using MongoDB.Driver.Core.Misc; +using MongoDB.Driver.Core.TestHelpers.XunitExtensions; +using MongoDB.Driver.Linq; +using MongoDB.TestHelpers.XunitExtensions; +using Xunit; + +namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira +{ + public class CSharp4743Tests : Linq3IntegrationTest + { + [Theory] + [ParameterAttributeData] + public void Where_using_DateTime_Date_should_work( + [Values(LinqProvider.V2, LinqProvider.V3)] LinqProvider linqProvider) + { + RequireServer.Check().Supports(Feature.DateOperatorsNewIn50); + var collection = GetCollection(linqProvider); + var memberId = 1; + var startDateTime = new DateTime(2023, 08, 07, 1, 2, 3, DateTimeKind.Utc); + + var queryable = collection.AsQueryable() + .Where( + b => b.MemberId == memberId && + b.InteractionDate.HasValue && b.InteractionDate.Value.Date >= startDateTime.Date); + + if (linqProvider == LinqProvider.V2) + { + var exception = Record.Exception(() => Translate(collection, queryable)); + exception.Should().BeOfType(); + } + else + { + var stages = Translate(collection, queryable); + AssertStages(stages, "{ $match : { $and : [{ MemberId : 1 }, { InteractionDate : { $ne : null } }, { $expr : { $gte : [{ $dateTrunc : { date : '$InteractionDate', unit : 'day' } }, ISODate('2023-08-07')] } }] } }"); + + var result = queryable.Single(); + result.Id.Should().Be(1); + } + } + + [Theory] + [ParameterAttributeData] + public void Where_using_DateTime_TimeOfDay_should_work( + [Values(LinqProvider.V2, LinqProvider.V3)] LinqProvider linqProvider) + { + RequireServer.Check().Supports(Feature.DateOperatorsNewIn50); + var collection = GetCollection(linqProvider); + var memberId = 1; + var startTimeOfDay = TimeSpan.FromHours(1); + + var queryable = collection.AsQueryable() + .Where( + b => b.MemberId == memberId && + b.InteractionDate.HasValue && b.InteractionDate.Value.TimeOfDay >= startTimeOfDay); + + if (linqProvider == LinqProvider.V2) + { + var exception = Record.Exception(() => Translate(collection, queryable)); + exception.Should().BeOfType(); + } + else + { + var stages = Translate(collection, queryable); + AssertStages(stages, "{ $match : { $and : [{ MemberId : 1 }, { InteractionDate : { $ne : null } }, { $expr : { $gte : [{ $dateDiff : { startDate : { $dateTrunc : { date : '$InteractionDate', unit : 'day' } }, endDate : '$InteractionDate', unit : 'millisecond' } }, { $numberLong : 3600000 }] } }] } }"); + + var result = queryable.Single(); + result.Id.Should().Be(1); + } + } + + [Theory] + [ParameterAttributeData] + public void Where_using_DateTime_Year_should_work( + [Values(LinqProvider.V2, LinqProvider.V3)] LinqProvider linqProvider) + { + var collection = GetCollection(linqProvider); + var memberId = 1; + var startDateTime = new DateTime(2023, 08, 07, 0, 0, 0, DateTimeKind.Utc); + + var queryable = collection.AsQueryable() + .Where( + b => b.MemberId == memberId && + b.InteractionDate.HasValue && b.InteractionDate.Value.Year >= startDateTime.Year); + + if (linqProvider == LinqProvider.V2) + { + var exception = Record.Exception(() => Translate(collection, queryable)); + exception.Should().BeOfType(); + } + else + { + var stages = Translate(collection, queryable); + AssertStages(stages, "{ $match : { $and : [{ MemberId : 1 }, { InteractionDate : { $ne : null } }, { $expr : { $gte : [{ $year : '$InteractionDate' }, 2023] } }] } }"); + + var result = queryable.Single(); + result.Id.Should().Be(1); + } + } + + private IMongoCollection GetCollection(LinqProvider linqProvider) + { + var collection = GetCollection("test", linqProvider); + CreateCollection( + collection, + new C { Id = 1, MemberId = 1, InteractionDate = new DateTime(2023, 08, 07, 1, 2, 3, DateTimeKind.Utc) }); + return collection; + } + + private class C + { + public int Id { get; set; } + public int MemberId { get; set; } + public DateTime? InteractionDate { get; set; } + } + } +}