未验证 提交 0f72c543 编写于 作者: S Shay Rojansky 提交者: GitHub

Translate DateTime.TimeOfDay and NodaTime LocalDateTime.Time (#2802)

Closes #2801
上级 555f972d
......@@ -282,74 +282,42 @@ SqlExpression Upper()
}
private SqlExpression? TranslateDateTime(SqlExpression instance, MemberInfo member, Type returnType)
{
switch (member.Name)
=> member.Name switch
{
case "Year":
case "Years":
return GetDatePartExpression(instance, "year");
case "Month":
case "Months":
return GetDatePartExpression(instance, "month");
case "DayOfYear":
return GetDatePartExpression(instance, "doy");
case "Day":
case "Days":
return GetDatePartExpression(instance, "day");
case "Hour":
case "Hours":
return GetDatePartExpression(instance, "hour");
case "Minute":
case "Minutes":
return GetDatePartExpression(instance, "minute");
case "Second":
case "Seconds":
return GetDatePartExpression(instance, "second", true);
case "Millisecond":
case "Milliseconds":
return null; // Too annoying
case "DayOfWeek":
// Unlike DateTime.DayOfWeek, NodaTime's IsoDayOfWeek enum doesn't exactly correspond to PostgreSQL's
// values returned by date_part('dow', ...): in NodaTime Sunday is 7 and not 0, which is None.
// So we generate a CASE WHEN expression to translate PostgreSQL's 0 to 7.
var getValueExpression = GetDatePartExpression(instance, "dow", true);
// TODO: Can be simplified once https://github.com/aspnet/EntityFrameworkCore/pull/16726 is in
return
_sqlExpressionFactory.Case(
new[]
{
new CaseWhenClause(
_sqlExpressionFactory.Equal(getValueExpression, _sqlExpressionFactory.Constant(0)),
_sqlExpressionFactory.Constant(7))
},
getValueExpression
);
"Year" or "Years" => GetDatePartExpression(instance, "year"),
"Month" or "Months" => GetDatePartExpression(instance, "month"),
"DayOfYear" => GetDatePartExpression(instance, "doy"),
"Day" or "Days" => GetDatePartExpression(instance, "day"),
"Hour" or "Hours" => GetDatePartExpression(instance, "hour"),
"Minute" or "Minutes" => GetDatePartExpression(instance, "minute"),
"Second" or "Seconds" => GetDatePartExpression(instance, "second", true),
"Millisecond" or "Milliseconds" => null, // Too annoying
// Unlike DateTime.DayOfWeek, NodaTime's IsoDayOfWeek enum doesn't exactly correspond to PostgreSQL's
// values returned by date_part('dow', ...): in NodaTime Sunday is 7 and not 0, which is None.
// So we generate a CASE WHEN expression to translate PostgreSQL's 0 to 7.
"DayOfWeek" when GetDatePartExpression(instance, "dow", true) is var getValueExpression
=> _sqlExpressionFactory.Case(
getValueExpression,
new[]
{
new CaseWhenClause(_sqlExpressionFactory.Constant(0), _sqlExpressionFactory.Constant(7))
},
getValueExpression),
// PG allows converting a timestamp directly to date, truncating the time; but given a timestamptz, it performs a time zone
// conversion (based on TimeZone), which we don't want (so avoid translating except on timestamp).
// The translation for ZonedDateTime.Date converts to timestamp before ending up here.
case "Date" when instance.TypeMapping is TimestampLocalDateTimeMapping or LegacyTimestampInstantMapping:
return _sqlExpressionFactory.Convert(instance, typeof(LocalDate), _typeMappingSource.FindMapping(typeof(LocalDate))!);
case "TimeOfDay":
// TODO: Technically possible simply via casting to PG time,
// but ExplicitCastExpression only allows casting to PG types that
// are default-mapped from CLR types (timespan maps to interval,
// which timestamp cannot be cast into)
return null;
default:
return null;
}
}
"Date" when instance.TypeMapping is TimestampLocalDateTimeMapping or LegacyTimestampInstantMapping
=> _sqlExpressionFactory.Convert(instance, typeof(LocalDate), _typeMappingSource.FindMapping(typeof(LocalDate))!),
"TimeOfDay" => _sqlExpressionFactory.Convert(
instance,
typeof(LocalTime),
_typeMappingSource.FindMapping(typeof(LocalTime), storeTypeName: "time")),
_ => null
};
/// <summary>
/// Constructs the date_part expression.
......
......@@ -12,6 +12,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionTranslators.Inte
/// </remarks>
public class NpgsqlDateTimeMemberTranslator : IMemberTranslator
{
private readonly IRelationalTypeMappingSource _typeMappingSource;
private readonly NpgsqlSqlExpressionFactory _sqlExpressionFactory;
private readonly RelationalTypeMapping _timestampMapping;
private readonly RelationalTypeMapping _timestampTzMapping;
......@@ -26,6 +27,7 @@ public class NpgsqlDateTimeMemberTranslator : IMemberTranslator
IRelationalTypeMappingSource typeMappingSource,
NpgsqlSqlExpressionFactory sqlExpressionFactory)
{
_typeMappingSource = typeMappingSource;
_timestampMapping = typeMappingSource.FindMapping("timestamp without time zone")!;
_timestampTzMapping = typeMappingSource.FindMapping("timestamp with time zone")!;
_sqlExpressionFactory = sqlExpressionFactory;
......@@ -128,11 +130,10 @@ public class NpgsqlDateTimeMemberTranslator : IMemberTranslator
// .NET's DayOfWeek is an enum, but its int values happen to correspond to PostgreSQL
nameof(DateTime.DayOfWeek) => GetDatePartExpression(instance!, "dow", floor: true),
// TODO: Technically possible simply via casting to PG time, should be better in EF Core 3.0
// but ExplicitCastExpression only allows casting to PG types that
// are default-mapped from CLR types (timespan maps to interval,
// which timestamp cannot be cast into)
nameof(DateTime.TimeOfDay) => null,
nameof(DateTime.TimeOfDay) => _sqlExpressionFactory.Convert(
instance!,
typeof(TimeSpan),
_typeMappingSource.FindMapping(typeof(TimeSpan), storeTypeName: "time")),
// TODO: Should be possible
nameof(DateTime.Ticks) => null,
......
......@@ -33,6 +33,17 @@ WHERE date_trunc('day', now()::timestamp) = date_trunc('day', now()::timestamp)
""");
}
public override async Task Time_of_day_datetime(bool async)
{
await base.Time_of_day_datetime(async);
AssertSql(
"""
SELECT o."OrderDate"::time
FROM "Orders" AS o
""");
}
public override async Task Where_datetime_date_component(bool async)
{
await base.Where_datetime_date_component(async);
......
......@@ -311,6 +311,23 @@ public async Task LocalDateTime_Date(bool async)
""");
}
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public async Task LocalDateTime_Time(bool async)
{
await AssertQuery(
async,
ss => ss.Set<NodaTimeTypes>().Where(t => t.LocalDateTime.TimeOfDay == new LocalTime(10, 31, 33, 666)),
entryCount: 1);
AssertSql(
"""
SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime"
FROM "NodaTimeTypes" AS n
WHERE n."LocalDateTime"::time = TIME '10:31:33.666'
""");
}
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public async Task LocalDateTime_DayOfWeek(bool async)
......@@ -324,8 +341,8 @@ public async Task LocalDateTime_DayOfWeek(bool async)
"""
SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime"
FROM "NodaTimeTypes" AS n
WHERE CASE
WHEN floor(date_part('dow', n."LocalDateTime"))::int = 0 THEN 7
WHERE CASE floor(date_part('dow', n."LocalDateTime"))::int
WHEN 0 THEN 7
ELSE floor(date_part('dow', n."LocalDateTime"))::int
END = 5
""");
......@@ -1812,8 +1829,8 @@ public async Task ZonedDateTime_DayOfWeek(bool async)
"""
SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime"
FROM "NodaTimeTypes" AS n
WHERE CASE
WHEN floor(date_part('dow', n."ZonedDateTime" AT TIME ZONE 'UTC'))::int = 0 THEN 7
WHERE CASE floor(date_part('dow', n."ZonedDateTime" AT TIME ZONE 'UTC'))::int
WHEN 0 THEN 7
ELSE floor(date_part('dow', n."ZonedDateTime" AT TIME ZONE 'UTC'))::int
END = 5
""");
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册