From c1d263aedc8b3c57a539213963e4f0b67d591853 Mon Sep 17 00:00:00 2001 From: Christopher Jolly Date: Sun, 8 Oct 2023 01:28:32 +0800 Subject: [PATCH] Initial support for DateOnly/TimeOnly --- src/EFCore.Jet.Data/JetDataReader.cs | 19 +- .../Internal/JetDateOnlyMemberTranslator.cs | 63 ++++++ .../Internal/JetDateOnlyMethodTranslator.cs | 77 +++++++ .../Internal/JetMemberTranslatorProvider.cs | 6 +- .../JetMethodCallTranslatorProvider.cs | 2 + .../Internal/JetTimeOnlyMemberTranslator.cs | 66 ++++++ .../Internal/JetTimeOnlyMethodTranslator.cs | 103 +++++++++ .../Internal/JetDateOnlyTypeMapping.cs | 94 ++++++++ .../Internal/JetTimeOnlyTypeMapping.cs | 58 +++++ .../Storage/Internal/JetTypeMappingSource.cs | 18 +- .../Query/GearsOfWarQueryJetFixture.cs | 13 +- .../Query/GearsOfWarQueryJetTest.cs | 191 ++++++++++------ ...orthwindFunctionsQueryJetTest.Functions.cs | 6 +- .../Query/TPCGearsOfWarQueryJetFixture.cs | 4 +- .../Query/TPCGearsOfWarQueryJetTest.cs | 207 ++++++++++++------ .../Query/TPTGearsOfWarQueryJetFixture.cs | 4 +- .../Query/TPTGearsOfWarQueryJetTest.cs | 195 +++++++++++------ 17 files changed, 890 insertions(+), 236 deletions(-) create mode 100644 src/EFCore.Jet/Query/ExpressionTranslators/Internal/JetDateOnlyMemberTranslator.cs create mode 100644 src/EFCore.Jet/Query/ExpressionTranslators/Internal/JetDateOnlyMethodTranslator.cs create mode 100644 src/EFCore.Jet/Query/ExpressionTranslators/Internal/JetTimeOnlyMemberTranslator.cs create mode 100644 src/EFCore.Jet/Query/ExpressionTranslators/Internal/JetTimeOnlyMethodTranslator.cs create mode 100644 src/EFCore.Jet/Storage/Internal/JetDateOnlyTypeMapping.cs create mode 100644 src/EFCore.Jet/Storage/Internal/JetTimeOnlyTypeMapping.cs diff --git a/src/EFCore.Jet.Data/JetDataReader.cs b/src/EFCore.Jet.Data/JetDataReader.cs index 8a1e43f..79f728b 100644 --- a/src/EFCore.Jet.Data/JetDataReader.cs +++ b/src/EFCore.Jet.Data/JetDataReader.cs @@ -176,7 +176,16 @@ namespace EntityFrameworkCore.Jet.Data return (DateTime)value; } - return (DateTime)value; + public DateOnly GetDateOnly(int ordinal) + { + var value = GetDateTime(ordinal); + return DateOnly.FromDateTime(value); + } + + public TimeOnly GetTimeOnly(int ordinal) + { + var value = GetDateTime(ordinal); + return TimeOnly.FromDateTime(value); } public virtual TimeSpan GetTimeSpan(int ordinal) @@ -472,6 +481,14 @@ namespace EntityFrameworkCore.Jet.Data { return (T)(object)GetDateTime(ordinal); } + if (typeof(T) == typeof(DateOnly)) + { + return (T)(object)GetDateOnly(ordinal); + } + if (typeof(T) == typeof(TimeOnly)) + { + return (T)(object)GetTimeOnly(ordinal); + } if (typeof(T) == typeof(TimeSpan)) { return (T)(object)GetTimeSpan(ordinal); diff --git a/src/EFCore.Jet/Query/ExpressionTranslators/Internal/JetDateOnlyMemberTranslator.cs b/src/EFCore.Jet/Query/ExpressionTranslators/Internal/JetDateOnlyMemberTranslator.cs new file mode 100644 index 0000000..1f1379f --- /dev/null +++ b/src/EFCore.Jet/Query/ExpressionTranslators/Internal/JetDateOnlyMemberTranslator.cs @@ -0,0 +1,63 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; + +namespace EntityFrameworkCore.Jet.Query.ExpressionTranslators.Internal; + +/// +/// 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. +/// +public class JetDateOnlyMemberTranslator : IMemberTranslator +{ + private static readonly Dictionary DatePartMapping + = new() + { + { nameof(DateTime.Year), "yyyy" }, + { nameof(DateTime.Month), "m" }, + { nameof(DateTime.DayOfYear), "y" }, + { nameof(DateTime.Day), "d" } + }; + + private readonly ISqlExpressionFactory _sqlExpressionFactory; + + /// + /// 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. + /// + public JetDateOnlyMemberTranslator(ISqlExpressionFactory sqlExpressionFactory) + { + _sqlExpressionFactory = sqlExpressionFactory; + } + + /// + /// 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. + /// + public virtual SqlExpression? Translate( + SqlExpression? instance, + MemberInfo member, + Type returnType, + IDiagnosticsLogger logger) + => member.DeclaringType == typeof(DateOnly) && DatePartMapping.TryGetValue(member.Name, out var datePart) + ? _sqlExpressionFactory.Function( + "DATEPART", + new[] { _sqlExpressionFactory.Constant(datePart), instance! }, + nullable: true, + argumentsPropagateNullability: new[] { false, true }, + returnType) + : null; +} diff --git a/src/EFCore.Jet/Query/ExpressionTranslators/Internal/JetDateOnlyMethodTranslator.cs b/src/EFCore.Jet/Query/ExpressionTranslators/Internal/JetDateOnlyMethodTranslator.cs new file mode 100644 index 0000000..e51c188 --- /dev/null +++ b/src/EFCore.Jet/Query/ExpressionTranslators/Internal/JetDateOnlyMethodTranslator.cs @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; + +namespace EntityFrameworkCore.Jet.Query.ExpressionTranslators.Internal; + +/// +/// 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. +/// +public class JetDateOnlyMethodTranslator : IMethodCallTranslator +{ + private readonly Dictionary _methodInfoDatePartMapping = new() + { + { typeof(DateOnly).GetRuntimeMethod(nameof(DateOnly.AddYears), new[] { typeof(int) })!, "yyyy" }, + { typeof(DateOnly).GetRuntimeMethod(nameof(DateOnly.AddMonths), new[] { typeof(int) })!, "m" }, + { typeof(DateOnly).GetRuntimeMethod(nameof(DateOnly.AddDays), new[] { typeof(int) })!, "d" } + }; + + private readonly ISqlExpressionFactory _sqlExpressionFactory; + + /// + /// 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. + /// + public JetDateOnlyMethodTranslator(ISqlExpressionFactory sqlExpressionFactory) + { + _sqlExpressionFactory = sqlExpressionFactory; + } + + /// + /// 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. + /// + public virtual SqlExpression? Translate( + SqlExpression? instance, + MethodInfo method, + IReadOnlyList arguments, + IDiagnosticsLogger logger) + { + if (_methodInfoDatePartMapping.TryGetValue(method, out var datePart) + && instance != null) + { + instance = _sqlExpressionFactory.ApplyDefaultTypeMapping(instance); + + return _sqlExpressionFactory.Function( + "DATEADD", + new[] { _sqlExpressionFactory.Constant(datePart), _sqlExpressionFactory.Convert(arguments[0], typeof(int)), instance }, + nullable: true, + argumentsPropagateNullability: new[] { false, true, true }, + instance.Type, + instance.TypeMapping); + } + + if (method.DeclaringType == typeof(DateOnly) + && method.Name == nameof(DateOnly.FromDateTime) + && arguments.Count == 1) + { + return _sqlExpressionFactory.Convert(arguments[0], typeof(DateOnly)); + } + + return null; + } +} diff --git a/src/EFCore.Jet/Query/ExpressionTranslators/Internal/JetMemberTranslatorProvider.cs b/src/EFCore.Jet/Query/ExpressionTranslators/Internal/JetMemberTranslatorProvider.cs index 9ad36af..4e00771 100644 --- a/src/EFCore.Jet/Query/ExpressionTranslators/Internal/JetMemberTranslatorProvider.cs +++ b/src/EFCore.Jet/Query/ExpressionTranslators/Internal/JetMemberTranslatorProvider.cs @@ -23,9 +23,11 @@ namespace EntityFrameworkCore.Jet.Query.ExpressionTranslators.Internal // ReSharper disable once VirtualMemberCallInConstructor AddTranslators(new IMemberTranslator[] { - new JetStringMemberTranslator(sqlExpressionFactory), + new JetDateOnlyMemberTranslator(sqlExpressionFactory), new JetDateTimeMemberTranslator(sqlExpressionFactory), - new JetTimeSpanMemberTranslator(sqlExpressionFactory) + new JetStringMemberTranslator(sqlExpressionFactory), + new JetTimeSpanMemberTranslator(sqlExpressionFactory), + new JetTimeOnlyMemberTranslator(sqlExpressionFactory) }); } } diff --git a/src/EFCore.Jet/Query/ExpressionTranslators/Internal/JetMethodCallTranslatorProvider.cs b/src/EFCore.Jet/Query/ExpressionTranslators/Internal/JetMethodCallTranslatorProvider.cs index 5096512..07a424d 100644 --- a/src/EFCore.Jet/Query/ExpressionTranslators/Internal/JetMethodCallTranslatorProvider.cs +++ b/src/EFCore.Jet/Query/ExpressionTranslators/Internal/JetMethodCallTranslatorProvider.cs @@ -28,6 +28,7 @@ namespace EntityFrameworkCore.Jet.Query.ExpressionTranslators.Internal new JetByteArrayMethodTranslator(sqlExpressionFactory), new JetConvertTranslator(sqlExpressionFactory), new JetDateDiffFunctionsTranslator(sqlExpressionFactory), + new JetDateOnlyMethodTranslator(sqlExpressionFactory), new JetDateTimeMethodTranslator(sqlExpressionFactory), new JetIsDateFunctionTranslator(sqlExpressionFactory), new JetMathTranslator(sqlExpressionFactory), @@ -35,6 +36,7 @@ namespace EntityFrameworkCore.Jet.Query.ExpressionTranslators.Internal new JetObjectToStringTranslator(sqlExpressionFactory), new JetStringMethodTranslator(sqlExpressionFactory), new JetRandomTranslator(sqlExpressionFactory), + new JetTimeOnlyMethodTranslator(sqlExpressionFactory) }); } } diff --git a/src/EFCore.Jet/Query/ExpressionTranslators/Internal/JetTimeOnlyMemberTranslator.cs b/src/EFCore.Jet/Query/ExpressionTranslators/Internal/JetTimeOnlyMemberTranslator.cs new file mode 100644 index 0000000..1db98dc --- /dev/null +++ b/src/EFCore.Jet/Query/ExpressionTranslators/Internal/JetTimeOnlyMemberTranslator.cs @@ -0,0 +1,66 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; + +namespace EntityFrameworkCore.Jet.Query.ExpressionTranslators.Internal; + +/// +/// 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. +/// +public class JetTimeOnlyMemberTranslator : IMemberTranslator +{ + private static readonly Dictionary DatePartMappings = new() + { + { nameof(TimeOnly.Hour), "h" }, + { nameof(TimeOnly.Minute), "n" }, + { nameof(TimeOnly.Second), "s" }, + { nameof(TimeOnly.Millisecond), "millisecond" } + }; + + private readonly ISqlExpressionFactory _sqlExpressionFactory; + + /// + /// 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. + /// + public JetTimeOnlyMemberTranslator(ISqlExpressionFactory sqlExpressionFactory) + { + _sqlExpressionFactory = sqlExpressionFactory; + } + + /// + /// 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. + /// + public virtual SqlExpression? Translate( + SqlExpression? instance, + MemberInfo member, + Type returnType, + IDiagnosticsLogger logger) + { + if (member.DeclaringType == typeof(TimeOnly) && DatePartMappings.TryGetValue(member.Name, out var value)) + { + return _sqlExpressionFactory.Function( + "DATEPART", new[] { _sqlExpressionFactory.Constant(value), instance! }, + nullable: true, + argumentsPropagateNullability: new[] { false, true }, + returnType); + } + + return null; + } +} diff --git a/src/EFCore.Jet/Query/ExpressionTranslators/Internal/JetTimeOnlyMethodTranslator.cs b/src/EFCore.Jet/Query/ExpressionTranslators/Internal/JetTimeOnlyMethodTranslator.cs new file mode 100644 index 0000000..81eec0e --- /dev/null +++ b/src/EFCore.Jet/Query/ExpressionTranslators/Internal/JetTimeOnlyMethodTranslator.cs @@ -0,0 +1,103 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using ExpressionExtensions = Microsoft.EntityFrameworkCore.Query.ExpressionExtensions; + +namespace EntityFrameworkCore.Jet.Query.ExpressionTranslators.Internal; + +/// +/// 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. +/// +public class JetTimeOnlyMethodTranslator : IMethodCallTranslator +{ + private static readonly MethodInfo AddHoursMethod = typeof(TimeOnly).GetRuntimeMethod( + nameof(TimeOnly.AddHours), new[] { typeof(double) })!; + private static readonly MethodInfo AddMinutesMethod = typeof(TimeOnly).GetRuntimeMethod( + nameof(TimeOnly.AddMinutes), new[] { typeof(double) })!; + private static readonly MethodInfo IsBetweenMethod = typeof(TimeOnly).GetRuntimeMethod( + nameof(TimeOnly.IsBetween), new[] { typeof(TimeOnly), typeof(TimeOnly) })!; + + private readonly ISqlExpressionFactory _sqlExpressionFactory; + + /// + /// 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. + /// + public JetTimeOnlyMethodTranslator(ISqlExpressionFactory sqlExpressionFactory) + { + _sqlExpressionFactory = sqlExpressionFactory; + } + + /// + /// 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. + /// + public virtual SqlExpression? Translate( + SqlExpression? instance, + MethodInfo method, + IReadOnlyList arguments, + IDiagnosticsLogger logger) + { + if (method.DeclaringType != typeof(TimeOnly) || instance is null) + { + return null; + } + + if (method == AddHoursMethod || method == AddMinutesMethod) + { + var datePart = method == AddHoursMethod ? "h" : "n"; + + // Some Add methods accept a double, and SQL Server DateAdd does not accept number argument outside of int range + if (arguments[0] is SqlConstantExpression { Value: double and (<= int.MinValue or >= int.MaxValue) }) + { + return null; + } + + instance = _sqlExpressionFactory.ApplyDefaultTypeMapping(instance); + + var dadd = _sqlExpressionFactory.Function( + "DATEADD", + new[] { _sqlExpressionFactory.Constant(datePart), _sqlExpressionFactory.Convert(arguments[0], typeof(int)), instance }, + nullable: true, + argumentsPropagateNullability: new[] { false, true, true }, + instance.Type, + instance.TypeMapping); + + return _sqlExpressionFactory.Function("TIMEVALUE", new[] { dadd }, true, + argumentsPropagateNullability: new[] { true }, instance.Type, instance.TypeMapping); + } + + // Translate TimeOnly.IsBetween to a >= b AND a < c. + // Since a is evaluated multiple times, only translate for simple constructs (i.e. avoid duplicating complex subqueries). + if (method == IsBetweenMethod + && instance is ColumnExpression or SqlConstantExpression or SqlParameterExpression) + { + var typeMapping = ExpressionExtensions.InferTypeMapping(instance, arguments[0], arguments[1]); + instance = _sqlExpressionFactory.ApplyTypeMapping(instance, typeMapping); + + return _sqlExpressionFactory.And( + _sqlExpressionFactory.GreaterThanOrEqual( + instance, + _sqlExpressionFactory.ApplyTypeMapping(arguments[0], typeMapping)), + _sqlExpressionFactory.LessThan( + instance, + _sqlExpressionFactory.ApplyTypeMapping(arguments[1], typeMapping))); + } + + return null; + } +} diff --git a/src/EFCore.Jet/Storage/Internal/JetDateOnlyTypeMapping.cs b/src/EFCore.Jet/Storage/Internal/JetDateOnlyTypeMapping.cs new file mode 100644 index 0000000..d9a33c3 --- /dev/null +++ b/src/EFCore.Jet/Storage/Internal/JetDateOnlyTypeMapping.cs @@ -0,0 +1,94 @@ +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Data; +using System.Data.Common; +using System.Globalization; +using System.Reflection.Metadata; +using System.Text; +using EntityFrameworkCore.Jet.Data; +using EntityFrameworkCore.Jet.Infrastructure.Internal; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Storage; + +namespace EntityFrameworkCore.Jet.Storage.Internal +{ + public class JetDateOnlyTypeMapping : DateOnlyTypeMapping + { + private readonly IJetOptions _options; + + public JetDateOnlyTypeMapping( + [NotNull] string storeType, + [NotNull] IJetOptions options, + DbType? dbType = null) + : base(storeType, dbType ?? System.Data.DbType.DateTime) + { + _options = options; + } + + protected JetDateOnlyTypeMapping(RelationalTypeMappingParameters parameters, IJetOptions options) + : base(parameters) + { + _options = options; + } + + protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters) + => new JetDateOnlyTypeMapping(parameters, _options); + + protected override void ConfigureParameter(DbParameter parameter) + { + base.ConfigureParameter(parameter); + if (parameter.Value != null) + { + ((DateOnly)parameter.Value).Deconstruct(out int year, out int month, out int day); + parameter.Value = new DateTime(year, month, day); + } + } + + protected override string GenerateNonNullSqlLiteral(object value) + => GenerateNonNullSqlLiteral(value, false); + + public virtual string GenerateNonNullSqlLiteral(object value, bool defaultClauseCompatible) + { + var dateTime = ConvertToDateTimeCompatibleValue(value); + + dateTime = CheckDateTimeValue(dateTime); + + var literal = new StringBuilder(); + + literal.Append( + defaultClauseCompatible + ? "'" + : "#"); + + literal.AppendFormat(CultureInfo.InvariantCulture, "{0:yyyy-MM-dd}", dateTime); + literal.Append( + defaultClauseCompatible + ? "'" + : "#"); + + return literal.ToString(); + } + + protected virtual DateTime ConvertToDateTimeCompatibleValue(object value) + { + ((DateOnly)value).Deconstruct(out int year, out int month, out int day); + return new DateTime(year, month, day); + } + + private static DateTime CheckDateTimeValue(DateTime dateTime) + { + if (dateTime < JetConfiguration.TimeSpanOffset) + { + if (dateTime != default) + { + throw new InvalidOperationException($"The {nameof(DateTime)} value '{dateTime}' is smaller than the minimum supported value of '{JetConfiguration.TimeSpanOffset}'."); + } + + dateTime = JetConfiguration.TimeSpanOffset; + } + + return dateTime; + } + } +} \ No newline at end of file diff --git a/src/EFCore.Jet/Storage/Internal/JetTimeOnlyTypeMapping.cs b/src/EFCore.Jet/Storage/Internal/JetTimeOnlyTypeMapping.cs new file mode 100644 index 0000000..7273a25 --- /dev/null +++ b/src/EFCore.Jet/Storage/Internal/JetTimeOnlyTypeMapping.cs @@ -0,0 +1,58 @@ +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Data; +using System.Data.Common; +using EntityFrameworkCore.Jet.Data; +using EntityFrameworkCore.Jet.Infrastructure.Internal; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Storage; + +namespace EntityFrameworkCore.Jet.Storage.Internal +{ + public class JetTimeOnlyTypeMapping : TimeOnlyTypeMapping + { + [NotNull] private readonly IJetOptions _options; + + public JetTimeOnlyTypeMapping( + [NotNull] string storeType, + [NotNull] IJetOptions options) + : base(storeType, System.Data.DbType.DateTime) + { + _options = options; + } + + protected JetTimeOnlyTypeMapping(RelationalTypeMappingParameters parameters, IJetOptions options) + : base(parameters) + { + _options = options; + } + + protected override void ConfigureParameter(DbParameter parameter) + { + base.ConfigureParameter(parameter); + if (parameter.Value != null) + { + ((TimeOnly)parameter.Value).Deconstruct(out int hour, out int min, out int sec); + parameter.Value = JetConfiguration.TimeSpanOffset.Add(new TimeSpan(hour, min, sec)); + } + } + + protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters) + => new JetTimeOnlyTypeMapping(parameters, _options); + + protected override string GenerateNonNullSqlLiteral(object value) + { + //Format time without any milliseconds + if (!_options.EnableMillisecondsSupport) + { + return FormattableString.Invariant($@"TIMEVALUE('{value:HH\:mm\:ss}')"); + } + else + { + //TODO: Treat as double + return ""; + } + } + } +} \ No newline at end of file diff --git a/src/EFCore.Jet/Storage/Internal/JetTypeMappingSource.cs b/src/EFCore.Jet/Storage/Internal/JetTypeMappingSource.cs index 4ab25bd..012d5f8 100644 --- a/src/EFCore.Jet/Storage/Internal/JetTypeMappingSource.cs +++ b/src/EFCore.Jet/Storage/Internal/JetTypeMappingSource.cs @@ -45,8 +45,9 @@ namespace EntityFrameworkCore.Jet.Storage.Internal private readonly JetDateTimeTypeMapping _datetime; private readonly JetDateTimeOffsetTypeMapping _datetimeoffset; - private readonly JetDateTimeTypeMapping _date; - private readonly JetTimeSpanTypeMapping _time; + private readonly JetDateOnlyTypeMapping _dateonly; + private readonly JetTimeSpanTypeMapping _timespan; + private readonly JetTimeOnlyTypeMapping _timeonly; private readonly JetStringTypeMapping _fixedLengthUnicodeString = new JetStringTypeMapping("char", unicode: true); private readonly JetStringTypeMapping _variableLengthUnicodeString = new JetStringTypeMapping("varchar", unicode: true); @@ -87,8 +88,9 @@ namespace EntityFrameworkCore.Jet.Storage.Internal _datetime = new JetDateTimeTypeMapping("datetime", options, dbType: DbType.DateTime); _datetimeoffset = new JetDateTimeOffsetTypeMapping("datetime", options); - _date = new JetDateTimeTypeMapping("datetime", options, dbType: DbType.Date); - _time = new JetTimeSpanTypeMapping("datetime", options); + _dateonly = new JetDateOnlyTypeMapping("datetime", options, dbType: DbType.Date); + _timeonly = new JetTimeOnlyTypeMapping("datetime", options); + _timespan = new JetTimeSpanTypeMapping("datetime", options); _storeTypeMappings = new Dictionary(StringComparer.OrdinalIgnoreCase) @@ -147,8 +149,8 @@ namespace EntityFrameworkCore.Jet.Storage.Internal {"money", _currency}, {"datetime", _datetime}, - {"date", _date}, - {"time", _time}, + {"date", _dateonly}, + {"time", _timeonly}, {"char", _fixedLengthUnicodeString}, {"alphanumeric", _fixedLengthUnicodeString}, @@ -195,8 +197,10 @@ namespace EntityFrameworkCore.Jet.Storage.Internal {typeof(double), _double}, {typeof(decimal), _decimal}, // CHECK: Is this supported or do we need to use CURRENCY? {typeof(DateTime), _datetime}, + {typeof(DateOnly), _dateonly}, {typeof(DateTimeOffset), _datetimeoffset}, - {typeof(TimeSpan), _time}, + {typeof(TimeSpan), _timespan}, + {typeof(TimeOnly), _timeonly}, {typeof(Guid), _guid}, }; diff --git a/test/EFCore.Jet.FunctionalTests/Query/GearsOfWarQueryJetFixture.cs b/test/EFCore.Jet.FunctionalTests/Query/GearsOfWarQueryJetFixture.cs index 3424d36..8576224 100644 --- a/test/EFCore.Jet.FunctionalTests/Query/GearsOfWarQueryJetFixture.cs +++ b/test/EFCore.Jet.FunctionalTests/Query/GearsOfWarQueryJetFixture.cs @@ -22,22 +22,11 @@ namespace EntityFrameworkCore.Jet.FunctionalTests.Query modelBuilder.Entity().Property(g => g.Location).HasColumnType("varchar(100)"); - /*modelBuilder.Entity( - b => - { - // Full-text binary search - b.Property("BriefingDocument"); - b.Property("BriefingDocumentFileExtension").HasColumnType("nvarchar(16)"); - });*/ - - // No support yet for DateOnly/TimeOnly (#24507) modelBuilder.Entity( b => { - b.Ignore(m => m.Date); - b.Ignore(m => m.Time); //b.Ignore(m => m.Timeline); - //b.Ignore(m => m.Duration); + // b.Ignore(m => m.Duration); }); } diff --git a/test/EFCore.Jet.FunctionalTests/Query/GearsOfWarQueryJetTest.cs b/test/EFCore.Jet.FunctionalTests/Query/GearsOfWarQueryJetTest.cs index 80519fb..6a6ba65 100644 --- a/test/EFCore.Jet.FunctionalTests/Query/GearsOfWarQueryJetTest.cs +++ b/test/EFCore.Jet.FunctionalTests/Query/GearsOfWarQueryJetTest.cs @@ -6346,12 +6346,8 @@ FROM `Missions` AS `m` WHERE (({AssertSqlHelper.Parameter("@__start_0")} <= CAST(CONVERT(date, `m`.`Timeline`) AS datetimeoffset)) AND (`m`.`Timeline` < {AssertSqlHelper.Parameter("@__end_1")})) AND `m`.`Timeline` IN ('1902-01-02T10:00:00.1234567+01:30')"); } - public override async Task DateTimeOffsetNow_minus_timespan(bool async) - { - await base.DateTimeOffsetNow_minus_timespan(async); - - AssertSql(); - } + public override Task DateTimeOffsetNow_minus_timespan(bool async) + => AssertTranslationFailed(() => base.DateTimeOffsetNow_minus_timespan(async)); public override async Task Navigation_inside_interpolated_string_expanded(bool isAsync) { @@ -7477,7 +7473,7 @@ FROM `Missions` AS `m` AssertSql( """ -SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Duration`, `m`.`Rating`, `m`.`Timeline` +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` FROM `Missions` AS `m` WHERE DATEPART('h', `m`.`Duration`) = 1 """); @@ -7489,7 +7485,7 @@ WHERE DATEPART('h', `m`.`Duration`) = 1 AssertSql( """ -SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Duration`, `m`.`Rating`, `m`.`Timeline` +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` FROM `Missions` AS `m` WHERE DATEPART('n', `m`.`Duration`) = 1 """); @@ -7501,7 +7497,7 @@ WHERE DATEPART('n', `m`.`Duration`) = 1 AssertSql( """ -SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Duration`, `m`.`Rating`, `m`.`Timeline` +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` FROM `Missions` AS `m` WHERE DATEPART('s', `m`.`Duration`) = 1 """); @@ -7997,19 +7993,27 @@ ORDER BY `g`.`Nickname`, `g`.`SquadId`, `c`.`Name` await base.FirstOrDefault_on_empty_collection_of_DateTime_in_subquery(async); AssertSql( - """ -SELECT `g`.`Nickname`, COALESCE(( - SELECT TOP(1) `t1`.`IssueDate` - FROM `Tags` AS `t1` - WHERE `t1`.`GearNickName` = `g`.`FullName` - ORDER BY `t1`.`Id`), '0001-01-01T00:00:00.0000000') AS `invalidTagIssueDate` + """ +SELECT `g`.`Nickname`, IIF(( + SELECT TOP 1 `t1`.`IssueDate` + FROM `Tags` AS `t1` + WHERE `t1`.`GearNickName` = `g`.`FullName` + ORDER BY `t1`.`Id`) IS NULL, #1899-12-30#, ( + SELECT TOP 1 `t1`.`IssueDate` + FROM `Tags` AS `t1` + WHERE `t1`.`GearNickName` = `g`.`FullName` + ORDER BY `t1`.`Id`)) AS `invalidTagIssueDate` FROM `Gears` AS `g` LEFT JOIN `Tags` AS `t` ON `g`.`Nickname` = `t`.`GearNickName` AND `g`.`SquadId` = `t`.`GearSquadId` -WHERE `t`.`IssueDate` > COALESCE(( - SELECT TOP(1) `t0`.`IssueDate` - FROM `Tags` AS `t0` - WHERE `t0`.`GearNickName` = `g`.`FullName` - ORDER BY `t0`.`Id`), '0001-01-01T00:00:00.0000000') +WHERE `t`.`IssueDate` > IIF(( + SELECT TOP 1 `t0`.`IssueDate` + FROM `Tags` AS `t0` + WHERE `t0`.`GearNickName` = `g`.`FullName` + ORDER BY `t0`.`Id`) IS NULL, #1899-12-30#, ( + SELECT TOP 1 `t0`.`IssueDate` + FROM `Tags` AS `t0` + WHERE `t0`.`GearNickName` = `g`.`FullName` + ORDER BY `t0`.`Id`)) """); } @@ -8506,39 +8510,54 @@ ORDER BY `t`.`Id`, `t1`.`Nickname`, `t1`.`FullName`, `t1`.`HasSoulPatch` public override async Task Where_DateOnly_Year(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_DateOnly_Year(async)); + await base.Where_DateOnly_Year(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE DATEPART('yyyy', `m`.`Date`) = 1990 +"""); } public override async Task Where_DateOnly_Month(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_DateOnly_Month(async)); + await base.Where_DateOnly_Month(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE DATEPART('m', `m`.`Date`) = 11 +"""); } public override async Task Where_DateOnly_Day(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_DateOnly_Day(async)); + await base.Where_DateOnly_Day(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE DATEPART('d', `m`.`Date`) = 10 +"""); } public override async Task Where_DateOnly_DayOfYear(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_DateOnly_DayOfYear(async)); + await base.Where_DateOnly_DayOfYear(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE DATEPART('y', `m`.`Date`) = 314 +"""); } public override async Task Where_DateOnly_DayOfWeek(bool async) { - // DateOnly and TimeOnly. Issue #24507. await AssertTranslationFailed(() => base.Where_DateOnly_DayOfWeek(async)); AssertSql(); @@ -8546,79 +8565,114 @@ ORDER BY `t`.`Id`, `t1`.`Nickname`, `t1`.`FullName`, `t1`.`HasSoulPatch` public override async Task Where_DateOnly_AddYears(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_DateOnly_AddYears(async)); + await base.Where_DateOnly_AddYears(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE DATEADD('yyyy', CLNG(3), `m`.`Date`) = #1993-11-10# +"""); } public override async Task Where_DateOnly_AddMonths(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_DateOnly_AddMonths(async)); + await base.Where_DateOnly_AddMonths(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE DATEADD('m', CLNG(3), `m`.`Date`) = #1991-02-10# +"""); } public override async Task Where_DateOnly_AddDays(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_DateOnly_AddDays(async)); + await base.Where_DateOnly_AddDays(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE DATEADD('d', CLNG(3), `m`.`Date`) = #1990-11-13# +"""); } public override async Task Where_TimeOnly_Hour(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_TimeOnly_Hour(async)); + await base.Where_TimeOnly_Hour(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE DATEPART('h', `m`.`Time`) = 10 +"""); } public override async Task Where_TimeOnly_Minute(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_TimeOnly_Minute(async)); + await base.Where_TimeOnly_Minute(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE DATEPART('n', `m`.`Time`) = 15 +"""); } public override async Task Where_TimeOnly_Second(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_TimeOnly_Second(async)); + await base.Where_TimeOnly_Second(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE DATEPART('s', `m`.`Time`) = 50 +"""); } public override async Task Where_TimeOnly_Millisecond(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_TimeOnly_Millisecond(async)); + await base.Where_TimeOnly_Millisecond(async); - AssertSql(); + AssertSql( + """ +SELECT [m].[Id], [m].[BriefingDocument], [m].[BriefingDocumentFileExtension], [m].[CodeName], [m].[Date], [m].[Duration], [m].[Rating], [m].[Time], [m].[Timeline] +FROM [Missions] AS [m] +WHERE DATEPART(millisecond, [m].[Time]) = 500 +"""); } public override async Task Where_TimeOnly_AddHours(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_TimeOnly_AddHours(async)); + await base.Where_TimeOnly_AddHours(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE TIMEVALUE(DATEADD('h', CLNG(3.0), `m`.`Time`)) = TIMEVALUE('13:15:50') +"""); } public override async Task Where_TimeOnly_AddMinutes(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_TimeOnly_AddMinutes(async)); + await base.Where_TimeOnly_AddMinutes(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE TIMEVALUE(DATEADD('n', CLNG(3.0), `m`.`Time`)) = TIMEVALUE('10:18:50') +"""); } public override async Task Where_TimeOnly_Add_TimeSpan(bool async) { - // DateOnly and TimeOnly. Issue #24507. await AssertTranslationFailed(() => base.Where_TimeOnly_Add_TimeSpan(async)); AssertSql(); @@ -8626,15 +8680,18 @@ ORDER BY `t`.`Id`, `t1`.`Nickname`, `t1`.`FullName`, `t1`.`HasSoulPatch` public override async Task Where_TimeOnly_IsBetween(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_TimeOnly_IsBetween(async)); + await base.Where_TimeOnly_IsBetween(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE (IIF(`m`.`Time` >= TIMEVALUE('10:00:00'), TRUE, FALSE) BAND IIF(`m`.`Time` < TIMEVALUE('11:00:00'), TRUE, FALSE)) = TRUE +"""); } public override async Task Where_TimeOnly_subtract_TimeOnly(bool async) { - // DateOnly and TimeOnly. Issue #24507. await AssertTranslationFailed(() => base.Where_TimeOnly_subtract_TimeOnly(async)); AssertSql(); @@ -8898,8 +8955,8 @@ ORDER BY `f`.`Id` await base.Where_equals_method_on_nullable_with_object_overload(async); AssertSql( - """ -SELECT `m`.`Id`, `m`.`BriefingDocument`, `m`.`BriefingDocumentFileExtension`, `m`.`CodeName`, `m`.`Duration`, `m`.`Rating`, `m`.`Timeline` + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` FROM `Missions` AS `m` WHERE `m`.`Rating` IS NULL """); diff --git a/test/EFCore.Jet.FunctionalTests/Query/NorthwindFunctionsQueryJetTest.Functions.cs b/test/EFCore.Jet.FunctionalTests/Query/NorthwindFunctionsQueryJetTest.Functions.cs index 8252ea6..ce78b04 100644 --- a/test/EFCore.Jet.FunctionalTests/Query/NorthwindFunctionsQueryJetTest.Functions.cs +++ b/test/EFCore.Jet.FunctionalTests/Query/NorthwindFunctionsQueryJetTest.Functions.cs @@ -2318,9 +2318,9 @@ WHERE 0 = 1"); AssertSql( """ -SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] -FROM [Orders] AS [o] -WHERE [o].[OrderDate] IS NOT NULL AND CAST([o].[OrderDate] AS date) = '1996-09-16' +SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate` +FROM `Orders` AS `o` +WHERE `o`.`OrderDate` IS NOT NULL AND `o`.`OrderDate` = #1996-09-16# """); } diff --git a/test/EFCore.Jet.FunctionalTests/Query/TPCGearsOfWarQueryJetFixture.cs b/test/EFCore.Jet.FunctionalTests/Query/TPCGearsOfWarQueryJetFixture.cs index 59fd192..faf9202 100644 --- a/test/EFCore.Jet.FunctionalTests/Query/TPCGearsOfWarQueryJetFixture.cs +++ b/test/EFCore.Jet.FunctionalTests/Query/TPCGearsOfWarQueryJetFixture.cs @@ -27,8 +27,8 @@ public class TPCGearsOfWarQueryJetFixture : TPCGearsOfWarQueryRelationalFixture modelBuilder.Entity( b => { - b.Ignore(m => m.Date); - b.Ignore(m => m.Time); + //b.Ignore(m => m.Date); + //b.Ignore(m => m.Time); }); } diff --git a/test/EFCore.Jet.FunctionalTests/Query/TPCGearsOfWarQueryJetTest.cs b/test/EFCore.Jet.FunctionalTests/Query/TPCGearsOfWarQueryJetTest.cs index e5dba37..69021da 100644 --- a/test/EFCore.Jet.FunctionalTests/Query/TPCGearsOfWarQueryJetTest.cs +++ b/test/EFCore.Jet.FunctionalTests/Query/TPCGearsOfWarQueryJetTest.cs @@ -10302,10 +10302,10 @@ FROM [Missions] AS [m] await base.Where_TimeSpan_Hours(async); AssertSql( - """ -SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Duration`, `m`.`Rating`, `m`.`Timeline` + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` FROM `Missions` AS `m` -WHERE DATEPART('s', `m`.`Duration`) = 1 +WHERE DATEPART('h', `m`.`Duration`) = 1 """); } @@ -10314,8 +10314,8 @@ WHERE DATEPART('s', `m`.`Duration`) = 1 await base.Where_TimeSpan_Minutes(async); AssertSql( -""" -SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Duration`, `m`.`Rating`, `m`.`Timeline` + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` FROM `Missions` AS `m` WHERE DATEPART('n', `m`.`Duration`) = 1 """); @@ -10326,8 +10326,8 @@ WHERE DATEPART('n', `m`.`Duration`) = 1 await base.Where_TimeSpan_Seconds(async); AssertSql( -""" -SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Duration`, `m`.`Rating`, `m`.`Timeline` + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` FROM `Missions` AS `m` WHERE DATEPART('s', `m`.`Duration`) = 1 """); @@ -11432,39 +11432,54 @@ ORDER BY IIF(`t`.`GearNickName` IS NOT NULL, `t0`.`SquadId`, NULL), `t`.`Note` public override async Task Where_DateOnly_Year(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_DateOnly_Year(async)); + await base.Where_DateOnly_Year(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE DATEPART('yyyy', `m`.`Date`) = 1990 +"""); } public override async Task Where_DateOnly_Month(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_DateOnly_Month(async)); + await base.Where_DateOnly_Month(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE DATEPART('m', `m`.`Date`) = 11 +"""); } public override async Task Where_DateOnly_Day(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_DateOnly_Day(async)); + await base.Where_DateOnly_Day(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE DATEPART('d', `m`.`Date`) = 10 +"""); } public override async Task Where_DateOnly_DayOfYear(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_DateOnly_DayOfYear(async)); + await base.Where_DateOnly_DayOfYear(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE DATEPART('y', `m`.`Date`) = 314 +"""); } public override async Task Where_DateOnly_DayOfWeek(bool async) { - // DateOnly and TimeOnly. Issue #24507. await AssertTranslationFailed(() => base.Where_DateOnly_DayOfWeek(async)); AssertSql(); @@ -11472,79 +11487,114 @@ ORDER BY IIF(`t`.`GearNickName` IS NOT NULL, `t0`.`SquadId`, NULL), `t`.`Note` public override async Task Where_DateOnly_AddYears(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_DateOnly_AddYears(async)); + await base.Where_DateOnly_AddYears(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE DATEADD('yyyy', CLNG(3), `m`.`Date`) = #1993-11-10# +"""); } public override async Task Where_DateOnly_AddMonths(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_DateOnly_AddMonths(async)); + await base.Where_DateOnly_AddMonths(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE DATEADD('m', CLNG(3), `m`.`Date`) = #1991-02-10# +"""); } public override async Task Where_DateOnly_AddDays(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_DateOnly_AddDays(async)); + await base.Where_DateOnly_AddDays(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE DATEADD('d', CLNG(3), `m`.`Date`) = #1990-11-13# +"""); } public override async Task Where_TimeOnly_Hour(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_TimeOnly_Hour(async)); + await base.Where_TimeOnly_Hour(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE DATEPART('h', `m`.`Time`) = 10 +"""); } public override async Task Where_TimeOnly_Minute(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_TimeOnly_Minute(async)); + await base.Where_TimeOnly_Minute(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE DATEPART('n', `m`.`Time`) = 15 +"""); } public override async Task Where_TimeOnly_Second(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_TimeOnly_Second(async)); + await base.Where_TimeOnly_Second(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE DATEPART('s', `m`.`Time`) = 50 +"""); } public override async Task Where_TimeOnly_Millisecond(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_TimeOnly_Millisecond(async)); + await base.Where_TimeOnly_Millisecond(async); - AssertSql(); + AssertSql( +""" +SELECT [m].[Id], [m].[CodeName], [m].[Date], [m].[Duration], [m].[Rating], [m].[Time], [m].[Timeline] +FROM [Missions] AS [m] +WHERE DATEPART(millisecond, [m].[Time]) = 500 +"""); } public override async Task Where_TimeOnly_AddHours(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_TimeOnly_AddHours(async)); + await base.Where_TimeOnly_AddHours(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE TIMEVALUE(DATEADD('h', CLNG(3.0), `m`.`Time`)) = TIMEVALUE('13:15:50') +"""); } public override async Task Where_TimeOnly_AddMinutes(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_TimeOnly_AddMinutes(async)); + await base.Where_TimeOnly_AddMinutes(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE TIMEVALUE(DATEADD('n', CLNG(3.0), `m`.`Time`)) = TIMEVALUE('10:18:50') +"""); } public override async Task Where_TimeOnly_Add_TimeSpan(bool async) { - // DateOnly and TimeOnly. Issue #24507. await AssertTranslationFailed(() => base.Where_TimeOnly_Add_TimeSpan(async)); AssertSql(); @@ -11552,15 +11602,18 @@ ORDER BY IIF(`t`.`GearNickName` IS NOT NULL, `t0`.`SquadId`, NULL), `t`.`Note` public override async Task Where_TimeOnly_IsBetween(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_TimeOnly_IsBetween(async)); + await base.Where_TimeOnly_IsBetween(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE (IIF(`m`.`Time` >= TIMEVALUE('10:00:00'), TRUE, FALSE) BAND IIF(`m`.`Time` < TIMEVALUE('11:00:00'), TRUE, FALSE)) = TRUE +"""); } public override async Task Where_TimeOnly_subtract_TimeOnly(bool async) { - // DateOnly and TimeOnly. Issue #24507. await AssertTranslationFailed(() => base.Where_TimeOnly_subtract_TimeOnly(async)); AssertSql(); @@ -11938,25 +11991,33 @@ WHERE `l`.`ServerAddress` = '127.0.0.1' await base.FirstOrDefault_on_empty_collection_of_DateTime_in_subquery(async); AssertSql( -""" -SELECT [t].[Nickname], COALESCE(( - SELECT TOP(1) [t2].[IssueDate] - FROM [Tags] AS [t2] - WHERE [t2].[GearNickName] = [t].[FullName] - ORDER BY [t2].[Id]), '0001-01-01T00:00:00.0000000') AS [invalidTagIssueDate] + """ +SELECT `t`.`Nickname`, IIF(( + SELECT TOP 1 `t2`.`IssueDate` + FROM `Tags` AS `t2` + WHERE `t2`.`GearNickName` = `t`.`FullName` + ORDER BY `t2`.`Id`) IS NULL, #1899-12-30#, ( + SELECT TOP 1 `t2`.`IssueDate` + FROM `Tags` AS `t2` + WHERE `t2`.`GearNickName` = `t`.`FullName` + ORDER BY `t2`.`Id`)) AS `invalidTagIssueDate` FROM ( - SELECT [g].[Nickname], [g].[SquadId], [g].[FullName] - FROM [Gears] AS [g] + SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`FullName` + FROM `Gears` AS `g` UNION ALL - SELECT [o].[Nickname], [o].[SquadId], [o].[FullName] - FROM [Officers] AS [o] -) AS [t] -LEFT JOIN [Tags] AS [t0] ON [t].[Nickname] = [t0].[GearNickName] AND [t].[SquadId] = [t0].[GearSquadId] -WHERE [t0].[IssueDate] > COALESCE(( - SELECT TOP(1) [t1].[IssueDate] - FROM [Tags] AS [t1] - WHERE [t1].[GearNickName] = [t].[FullName] - ORDER BY [t1].[Id]), '0001-01-01T00:00:00.0000000') + SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`FullName` + FROM `Officers` AS `o` +) AS `t` +LEFT JOIN `Tags` AS `t0` ON `t`.`Nickname` = `t0`.`GearNickName` AND `t`.`SquadId` = `t0`.`GearSquadId` +WHERE `t0`.`IssueDate` > IIF(( + SELECT TOP 1 `t1`.`IssueDate` + FROM `Tags` AS `t1` + WHERE `t1`.`GearNickName` = `t`.`FullName` + ORDER BY `t1`.`Id`) IS NULL, #1899-12-30#, ( + SELECT TOP 1 `t1`.`IssueDate` + FROM `Tags` AS `t1` + WHERE `t1`.`GearNickName` = `t`.`FullName` + ORDER BY `t1`.`Id`)) """); } @@ -12101,10 +12162,10 @@ ORDER BY [t].[Nickname], [t].[SquadId] await base.Where_equals_method_on_nullable_with_object_overload(async); AssertSql( -""" -SELECT [m].[Id], [m].[CodeName], [m].[Duration], [m].[Rating], [m].[Timeline] -FROM [Missions] AS [m] -WHERE [m].[Rating] IS NULL + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE `m`.`Rating` IS NULL """); } diff --git a/test/EFCore.Jet.FunctionalTests/Query/TPTGearsOfWarQueryJetFixture.cs b/test/EFCore.Jet.FunctionalTests/Query/TPTGearsOfWarQueryJetFixture.cs index 11f5d4e..52ecd4b 100644 --- a/test/EFCore.Jet.FunctionalTests/Query/TPTGearsOfWarQueryJetFixture.cs +++ b/test/EFCore.Jet.FunctionalTests/Query/TPTGearsOfWarQueryJetFixture.cs @@ -24,8 +24,8 @@ public class TPTGearsOfWarQueryJetFixture : TPTGearsOfWarQueryRelationalFixture modelBuilder.Entity( b => { - b.Ignore(m => m.Date); - b.Ignore(m => m.Time); + //b.Ignore(m => m.Date); + //b.Ignore(m => m.Time); }); } diff --git a/test/EFCore.Jet.FunctionalTests/Query/TPTGearsOfWarQueryJetTest.cs b/test/EFCore.Jet.FunctionalTests/Query/TPTGearsOfWarQueryJetTest.cs index f363ee8..befcd75 100644 --- a/test/EFCore.Jet.FunctionalTests/Query/TPTGearsOfWarQueryJetTest.cs +++ b/test/EFCore.Jet.FunctionalTests/Query/TPTGearsOfWarQueryJetTest.cs @@ -8385,10 +8385,10 @@ FROM [Missions] AS [m] await base.Where_TimeSpan_Hours(async); AssertSql( -""" -SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Duration`, `m`.`Rating`, `m`.`Timeline` + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` FROM `Missions` AS `m` -WHERE DATEPART('n', `m`.`Duration`) = 1 +WHERE DATEPART('h', `m`.`Duration`) = 1 """); } @@ -8398,7 +8398,7 @@ WHERE DATEPART('n', `m`.`Duration`) = 1 AssertSql( """ -SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Duration`, `m`.`Rating`, `m`.`Timeline` +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` FROM `Missions` AS `m` WHERE DATEPART('n', `m`.`Duration`) = 1 """); @@ -8410,7 +8410,7 @@ WHERE DATEPART('n', `m`.`Duration`) = 1 AssertSql( """ -SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Duration`, `m`.`Rating`, `m`.`Timeline` +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` FROM `Missions` AS `m` WHERE DATEPART('s', `m`.`Duration`) = 1 """); @@ -9259,39 +9259,54 @@ ORDER BY IIF(`t`.`GearNickName` IS NOT NULL, `t0`.`SquadId`, NULL), `t`.`Note` public override async Task Where_DateOnly_Year(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_DateOnly_Year(async)); + await base.Where_DateOnly_Year(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE DATEPART('yyyy', `m`.`Date`) = 1990 +"""); } public override async Task Where_DateOnly_Month(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_DateOnly_Month(async)); + await base.Where_DateOnly_Month(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE DATEPART('m', `m`.`Date`) = 11 +"""); } public override async Task Where_DateOnly_Day(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_DateOnly_Day(async)); + await base.Where_DateOnly_Day(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE DATEPART('d', `m`.`Date`) = 10 +"""); } public override async Task Where_DateOnly_DayOfYear(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_DateOnly_DayOfYear(async)); + await base.Where_DateOnly_DayOfYear(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE DATEPART('y', `m`.`Date`) = 314 +"""); } public override async Task Where_DateOnly_DayOfWeek(bool async) { - // DateOnly and TimeOnly. Issue #24507. await AssertTranslationFailed(() => base.Where_DateOnly_DayOfWeek(async)); AssertSql(); @@ -9299,79 +9314,114 @@ ORDER BY IIF(`t`.`GearNickName` IS NOT NULL, `t0`.`SquadId`, NULL), `t`.`Note` public override async Task Where_DateOnly_AddYears(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_DateOnly_AddYears(async)); + await base.Where_DateOnly_AddYears(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE DATEADD('yyyy', CLNG(3), `m`.`Date`) = #1993-11-10# +"""); } public override async Task Where_DateOnly_AddMonths(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_DateOnly_AddMonths(async)); + await base.Where_DateOnly_AddMonths(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE DATEADD('m', CLNG(3), `m`.`Date`) = #1991-02-10# +"""); } public override async Task Where_DateOnly_AddDays(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_DateOnly_AddDays(async)); + await base.Where_DateOnly_AddDays(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE DATEADD('d', CLNG(3), `m`.`Date`) = #1990-11-13# +"""); } public override async Task Where_TimeOnly_Hour(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_TimeOnly_Hour(async)); + await base.Where_TimeOnly_Hour(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE DATEPART('h', `m`.`Time`) = 10 +"""); } public override async Task Where_TimeOnly_Minute(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_TimeOnly_Minute(async)); + await base.Where_TimeOnly_Minute(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE DATEPART('n', `m`.`Time`) = 15 +"""); } public override async Task Where_TimeOnly_Second(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_TimeOnly_Second(async)); + await base.Where_TimeOnly_Second(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE DATEPART('s', `m`.`Time`) = 50 +"""); } public override async Task Where_TimeOnly_Millisecond(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_TimeOnly_Millisecond(async)); + await base.Where_TimeOnly_Millisecond(async); - AssertSql(); + AssertSql( +""" +SELECT [m].[Id], [m].[CodeName], [m].[Date], [m].[Duration], [m].[Rating], [m].[Time], [m].[Timeline] +FROM [Missions] AS [m] +WHERE DATEPART(millisecond, [m].[Time]) = 500 +"""); } public override async Task Where_TimeOnly_AddHours(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_TimeOnly_AddHours(async)); + await base.Where_TimeOnly_AddHours(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE TIMEVALUE(DATEADD('h', CLNG(3.0), `m`.`Time`)) = TIMEVALUE('13:15:50') +"""); } public override async Task Where_TimeOnly_AddMinutes(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_TimeOnly_AddMinutes(async)); + await base.Where_TimeOnly_AddMinutes(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE TIMEVALUE(DATEADD('n', CLNG(3.0), `m`.`Time`)) = TIMEVALUE('10:18:50') +"""); } public override async Task Where_TimeOnly_Add_TimeSpan(bool async) { - // DateOnly and TimeOnly. Issue #24507. await AssertTranslationFailed(() => base.Where_TimeOnly_Add_TimeSpan(async)); AssertSql(); @@ -9379,15 +9429,18 @@ ORDER BY IIF(`t`.`GearNickName` IS NOT NULL, `t0`.`SquadId`, NULL), `t`.`Note` public override async Task Where_TimeOnly_IsBetween(bool async) { - // DateOnly and TimeOnly. Issue #24507. - await AssertTranslationFailed(() => base.Where_TimeOnly_IsBetween(async)); + await base.Where_TimeOnly_IsBetween(async); - AssertSql(); + AssertSql( + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE (IIF(`m`.`Time` >= TIMEVALUE('10:00:00'), TRUE, FALSE) BAND IIF(`m`.`Time` < TIMEVALUE('11:00:00'), TRUE, FALSE)) = TRUE +"""); } public override async Task Where_TimeOnly_subtract_TimeOnly(bool async) { - // DateOnly and TimeOnly. Issue #24507. await AssertTranslationFailed(() => base.Where_TimeOnly_subtract_TimeOnly(async)); AssertSql(); @@ -9674,19 +9727,27 @@ WHERE `f`.`ServerAddress` = '127.0.0.1' await base.FirstOrDefault_on_empty_collection_of_DateTime_in_subquery(async); AssertSql( -""" -SELECT [g].[Nickname], COALESCE(( - SELECT TOP(1) [t1].[IssueDate] - FROM [Tags] AS [t1] - WHERE [t1].[GearNickName] = [g].[FullName] - ORDER BY [t1].[Id]), '0001-01-01T00:00:00.0000000') AS [invalidTagIssueDate] -FROM [Gears] AS [g] -LEFT JOIN [Tags] AS [t] ON [g].[Nickname] = [t].[GearNickName] AND [g].[SquadId] = [t].[GearSquadId] -WHERE [t].[IssueDate] > COALESCE(( - SELECT TOP(1) [t0].[IssueDate] - FROM [Tags] AS [t0] - WHERE [t0].[GearNickName] = [g].[FullName] - ORDER BY [t0].[Id]), '0001-01-01T00:00:00.0000000') + """ +SELECT `g`.`Nickname`, IIF(( + SELECT TOP 1 `t1`.`IssueDate` + FROM `Tags` AS `t1` + WHERE `t1`.`GearNickName` = `g`.`FullName` + ORDER BY `t1`.`Id`) IS NULL, #1899-12-30#, ( + SELECT TOP 1 `t1`.`IssueDate` + FROM `Tags` AS `t1` + WHERE `t1`.`GearNickName` = `g`.`FullName` + ORDER BY `t1`.`Id`)) AS `invalidTagIssueDate` +FROM `Gears` AS `g` +LEFT JOIN `Tags` AS `t` ON `g`.`Nickname` = `t`.`GearNickName` AND `g`.`SquadId` = `t`.`GearSquadId` +WHERE `t`.`IssueDate` > IIF(( + SELECT TOP 1 `t0`.`IssueDate` + FROM `Tags` AS `t0` + WHERE `t0`.`GearNickName` = `g`.`FullName` + ORDER BY `t0`.`Id`) IS NULL, #1899-12-30#, ( + SELECT TOP 1 `t0`.`IssueDate` + FROM `Tags` AS `t0` + WHERE `t0`.`GearNickName` = `g`.`FullName` + ORDER BY `t0`.`Id`)) """); } @@ -9825,10 +9886,10 @@ ORDER BY [g].[Nickname], [g].[SquadId] await base.Where_equals_method_on_nullable_with_object_overload(async); AssertSql( -""" -SELECT [m].[Id], [m].[CodeName], [m].[Duration], [m].[Rating], [m].[Timeline] -FROM [Missions] AS [m] -WHERE [m].[Rating] IS NULL + """ +SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` +FROM `Missions` AS `m` +WHERE `m`.`Rating` IS NULL """); }