Skip to content

Commit

Permalink
Cosmos: add translations for date/time components (#33976)
Browse files Browse the repository at this point in the history
Closes #33975
  • Loading branch information
roji authored Jun 14, 2024
1 parent f0a733d commit 5eec6fa
Show file tree
Hide file tree
Showing 14 changed files with 614 additions and 432 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.EntityFrameworkCore.Query.Internal;

namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;

/// <summary>
Expand All @@ -27,8 +29,9 @@ public CosmosMemberTranslatorProvider(
_plugins.AddRange(plugins.SelectMany(p => p.Translators));
_translators.AddRange(
[
new CosmosStringMemberTranslator(sqlExpressionFactory),
new CosmosDateTimeMemberTranslator(sqlExpressionFactory)
new CosmosDateTimeMemberTranslator(sqlExpressionFactory),
new CosmosNullableMemberTranslator(sqlExpressionFactory),
new CosmosStringMemberTranslator(sqlExpressionFactory)
]);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,12 @@ public CosmosMethodCallTranslatorProvider(

_translators.AddRange(
[
new CosmosDateTimeMethodTranslator(sqlExpressionFactory),
new CosmosEqualsTranslator(sqlExpressionFactory),
new CosmosStringMethodTranslator(sqlExpressionFactory),
new CosmosRandomTranslator(sqlExpressionFactory),
new CosmosMathTranslator(sqlExpressionFactory),
new CosmosRandomTranslator(sqlExpressionFactory),
new CosmosRegexTranslator(sqlExpressionFactory),
new CosmosStringMethodTranslator(sqlExpressionFactory),
new CosmosTypeCheckingTranslator(sqlExpressionFactory)
//new LikeTranslator(sqlExpressionFactory),
//new EnumHasFlagTranslator(sqlExpressionFactory),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,34 @@ public class CosmosDateTimeMemberTranslator(ISqlExpressionFactory sqlExpressionF
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
{
var declaringType = member.DeclaringType;
if ((declaringType == typeof(DateTime)
|| declaringType == typeof(DateTimeOffset))
&& member.Name == nameof(DateTime.UtcNow))

if (declaringType != typeof(DateTime) && declaringType != typeof(DateTimeOffset))
{
return sqlExpressionFactory.Function(
"GetCurrentDateTime",
[],
returnType);
return null;
}

return null;
return member.Name switch
{
nameof(DateTime.Year) => DatePart("yyyy"),
nameof(DateTime.Month) => DatePart("mm"),
nameof(DateTime.Day) => DatePart("dd"),
nameof(DateTime.Hour) => DatePart("hh"),
nameof(DateTime.Minute) => DatePart("mi"),
nameof(DateTime.Second) => DatePart("ss"),
nameof(DateTime.Millisecond) => DatePart("ms"),
nameof(DateTime.Microsecond) => DatePart("mcs"),
nameof(DateTime.Nanosecond) => DatePart("ns"),

nameof(DateTime.UtcNow)
=> sqlExpressionFactory.Function(
"GetCurrentDateTime",
[],
returnType),

_ => null
};

SqlFunctionExpression DatePart(string part)
=> sqlExpressionFactory.Function("DateTimePart", arguments: [sqlExpressionFactory.Constant(part), instance!], returnType);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

// ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public class CosmosDateTimeMethodTranslator(ISqlExpressionFactory sqlExpressionFactory) : IMethodCallTranslator
{
private static readonly Dictionary<MethodInfo, string> MethodInfoDatePartMapping = new()
{
{ typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddYears), [typeof(int)])!, "yyyy" },
{ typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddMonths), [typeof(int)])!, "mm" },
{ typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddDays), [typeof(double)])!, "dd" },
{ typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddHours), [typeof(double)])!, "hh" },
{ typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddMinutes), [typeof(double)])!, "mi" },
{ typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddSeconds), [typeof(double)])!, "ss" },
{ typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddMilliseconds), [typeof(double)])!, "ms" },
{ typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddMicroseconds), [typeof(double)])!, "mcs" },
{ typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddYears), [typeof(int)])!, "yyyy" },
{ typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddMonths), [typeof(int)])!, "mm" },
{ typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddDays), [typeof(double)])!, "dd" },
{ typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddHours), [typeof(double)])!, "hh" },
{ typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddMinutes), [typeof(double)])!, "mi" },
{ typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddSeconds), [typeof(double)])!, "ss" },
{ typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddMilliseconds), [typeof(double)])!, "ms" },
{ typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddMicroseconds), [typeof(double)])!, "mcs" }
};

private static readonly Dictionary<MethodInfo, string> MethodInfoDateDiffMapping = new()
{
{ typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.ToUnixTimeSeconds), Type.EmptyTypes)!, "second" },
{ typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.ToUnixTimeMilliseconds), Type.EmptyTypes)!, "millisecond" }
};

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public SqlExpression? Translate(
SqlExpression? instance,
MethodInfo method,
IReadOnlyList<SqlExpression> arguments,
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
{
if (method.DeclaringType != typeof(DateTime) && method.DeclaringType != typeof(DateTimeOffset))
{
return null;
}

if (MethodInfoDatePartMapping.TryGetValue(method, out var datePart)
&& instance != null)
{
return sqlExpressionFactory.Function(
"DateTimeAdd",
arguments: [sqlExpressionFactory.Constant(datePart), arguments[0], instance],
instance.Type,
instance.TypeMapping);
}

return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;

// ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore.Query.Internal;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public class CosmosNullableMemberTranslator(ISqlExpressionFactory sqlExpressionFactory) : IMemberTranslator
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual SqlExpression? Translate(
SqlExpression? instance,
MemberInfo member,
Type returnType,
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
{
if (member.DeclaringType?.IsNullableValueType() == true
&& instance != null)
{
return member.Name switch
{
nameof(Nullable<int>.Value) => instance,
nameof(Nullable<int>.HasValue) => sqlExpressionFactory.IsNotNull(instance),
_ => null
};
}

return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,8 @@ namespace Microsoft.EntityFrameworkCore.Query.Internal;
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public class NullableMemberTranslator : IMemberTranslator
public class NullableMemberTranslator(ISqlExpressionFactory sqlExpressionFactory) : IMemberTranslator
{
private readonly ISqlExpressionFactory _sqlExpressionFactory;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public NullableMemberTranslator(ISqlExpressionFactory sqlExpressionFactory)
{
_sqlExpressionFactory = sqlExpressionFactory;
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand All @@ -42,14 +29,12 @@ public NullableMemberTranslator(ISqlExpressionFactory sqlExpressionFactory)
if (member.DeclaringType?.IsNullableValueType() == true
&& instance != null)
{
switch (member.Name)
return member.Name switch
{
case nameof(Nullable<int>.Value):
return instance;

case nameof(Nullable<int>.HasValue):
return _sqlExpressionFactory.IsNotNull(instance);
}
nameof(Nullable<int>.Value) => instance,
nameof(Nullable<int>.HasValue) => sqlExpressionFactory.IsNotNull(instance),
_ => null
};
}

return null;
Expand Down
Loading

0 comments on commit 5eec6fa

Please sign in to comment.