Skip to content

Commit

Permalink
CSHARP-4743: Add support for DateTime.Date and DateTime.TimeOfDay.
Browse files Browse the repository at this point in the history
  • Loading branch information
rstam committed Aug 9, 2023
1 parent 208438f commit 5412f33
Show file tree
Hide file tree
Showing 2 changed files with 169 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<DayOfWeek>(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<DayOfWeek>(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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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<InvalidOperationException>();
}
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<InvalidOperationException>();
}
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<InvalidOperationException>();
}
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<C> GetCollection(LinqProvider linqProvider)
{
var collection = GetCollection<C>("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; }
}
}
}

0 comments on commit 5412f33

Please sign in to comment.