From ef1d7c6df8abefc0b10fd5e22893aa849b4acdd9 Mon Sep 17 00:00:00 2001 From: Olha Kramarenko Date: Mon, 19 Jan 2026 13:12:58 +0200 Subject: [PATCH 1/8] update configs --- Dependencies.targets | 8 ++++---- Directory.Build.props | 7 +++++++ Version.props | 2 +- .../EFCore.SingleStore.Json.Microsoft.csproj | 8 ++++---- .../EFCore.SingleStore.Json.Newtonsoft.csproj | 8 ++++---- src/EFCore.SingleStore.NTS/EFCore.SingleStore.NTS.csproj | 8 ++++---- src/EFCore.SingleStore/EFCore.SingleStore.csproj | 8 ++++---- 7 files changed, 28 insertions(+), 21 deletions(-) diff --git a/Dependencies.targets b/Dependencies.targets index b05de6291..75fdb612f 100644 --- a/Dependencies.targets +++ b/Dependencies.targets @@ -1,6 +1,6 @@ - [8.0.1,8.0.999] + [8.0.2,8.0.999] @@ -12,12 +12,12 @@ - + - - + + diff --git a/Directory.Build.props b/Directory.Build.props index 7984d8a57..b68a0b34d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,6 +2,13 @@ + + Debug + debug + $(DefineConstants);EFCORE_DEBUG_BUILD + $(DefineConstants);SINGLESTORECONNECTOR_DEBUG_BUILD + + EntityFrameworkCore.SingleStore singlestore;Entity Framework Core;entity-framework-core;ef;efcore;ef core;orm;sql diff --git a/Version.props b/Version.props index 3b74372dc..05660f9d4 100644 --- a/Version.props +++ b/Version.props @@ -12,7 +12,7 @@ - "rtm" - EF Core release independent, code quality production ready, major release - "servicing" - EF Core release independent, code quality production ready, mainly bugfixes --> - 8.0.1 + 8.0.2 servicing - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.dll - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Abstractions.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Abstractions.dll - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Analyzers.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Analyzers.dll - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Relational.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Relational.dll diff --git a/src/EFCore.SingleStore.Json.Newtonsoft/EFCore.SingleStore.Json.Newtonsoft.csproj b/src/EFCore.SingleStore.Json.Newtonsoft/EFCore.SingleStore.Json.Newtonsoft.csproj index 95b867016..06f673a8e 100644 --- a/src/EFCore.SingleStore.Json.Newtonsoft/EFCore.SingleStore.Json.Newtonsoft.csproj +++ b/src/EFCore.SingleStore.Json.Newtonsoft/EFCore.SingleStore.Json.Newtonsoft.csproj @@ -48,16 +48,16 @@ - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.dll - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Abstractions.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Abstractions.dll - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Analyzers.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Analyzers.dll - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Relational.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Relational.dll diff --git a/src/EFCore.SingleStore.NTS/EFCore.SingleStore.NTS.csproj b/src/EFCore.SingleStore.NTS/EFCore.SingleStore.NTS.csproj index 671c80b57..c9465c7a0 100644 --- a/src/EFCore.SingleStore.NTS/EFCore.SingleStore.NTS.csproj +++ b/src/EFCore.SingleStore.NTS/EFCore.SingleStore.NTS.csproj @@ -48,16 +48,16 @@ - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.dll - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Abstractions.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Abstractions.dll - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Analyzers.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Analyzers.dll - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Relational.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Relational.dll diff --git a/src/EFCore.SingleStore/EFCore.SingleStore.csproj b/src/EFCore.SingleStore/EFCore.SingleStore.csproj index 3bf900f1b..3a61616fe 100644 --- a/src/EFCore.SingleStore/EFCore.SingleStore.csproj +++ b/src/EFCore.SingleStore/EFCore.SingleStore.csproj @@ -26,16 +26,16 @@ - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.dll - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Abstractions.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Abstractions.dll - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Analyzers.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Analyzers.dll - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Relational.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Relational.dll From b6b9435845f02201ff7f9243939024fc6ec41f20 Mon Sep 17 00:00:00 2001 From: Olha Kramarenko Date: Mon, 19 Jan 2026 13:39:18 +0200 Subject: [PATCH 2/8] grab changes for Extensions, Infrastructure and Query blocks --- .../SingleStoreDbFunctionsExtensions.cs | 152 ++++++++++++++++++ .../SingleStorePropertyExtensions.cs | 43 +++-- .../Infrastructure/ServerVersionSupport.cs | 2 +- .../SingleStoreServerVersion.cs | 2 +- ...reDbFunctionsExtensionsMethodTranslator.cs | 58 +++++++ ...leStoreStringComparisonMethodTranslator.cs | 5 +- .../Internal/SingleStoreQuerySqlGenerator.cs | 27 +--- .../SingleStoreDateTimeMemberTranslator.cs | 22 +++ 8 files changed, 275 insertions(+), 36 deletions(-) diff --git a/src/EFCore.SingleStore/Extensions/SingleStoreDbFunctionsExtensions.cs b/src/EFCore.SingleStore/Extensions/SingleStoreDbFunctionsExtensions.cs index 95ca81215..091071be0 100644 --- a/src/EFCore.SingleStore/Extensions/SingleStoreDbFunctionsExtensions.cs +++ b/src/EFCore.SingleStore/Extensions/SingleStoreDbFunctionsExtensions.cs @@ -15,6 +15,158 @@ namespace Microsoft.EntityFrameworkCore /// public static class SingleStoreDbFunctionsExtensions { + #region ConvertTimeZone + + /// + /// Converts the `DateTime` value from the time zone given by to the time zone given by and returns the resulting value. + /// Corresponds to `CONVERT_TZ(dateTime, fromTimeZone, toTimeZone)`. + /// + /// The DbFunctions instance. + /// The `DateTime` value to convert. + /// The time zone to convert from. + /// The time zone to convert to. + /// The converted value. + public static DateTime? ConvertTimeZone( + [CanBeNull] this DbFunctions _, + DateTime dateTime, + string fromTimeZone, + string toTimeZone) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ConvertTimeZone))); + + /// + /// Converts the `DateOnly` value from the time zone given by to the time zone given by and returns the resulting value. + /// Corresponds to `CONVERT_TZ(dateTime, fromTimeZone, toTimeZone)`.. + /// + /// The DbFunctions instance. + /// The `DateOnly` value to convert. + /// The time zone to convert from. + /// The time zone to convert to. + /// The converted value. + public static DateOnly? ConvertTimeZone( + [CanBeNull] this DbFunctions _, + DateOnly dateOnly, + string fromTimeZone, + string toTimeZone) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ConvertTimeZone))); + + /// + /// Converts the `DateTime?` value from the time zone given by to the time zone given by and returns the resulting value. + /// Corresponds to `CONVERT_TZ(dateTime, fromTimeZone, toTimeZone)`. + /// + /// The DbFunctions instance. + /// The `DateTime?` value to convert. + /// The time zone to convert from. + /// The time zone to convert to. + /// The converted value. + public static DateTime? ConvertTimeZone( + [CanBeNull] this DbFunctions _, + DateTime? dateTime, + string fromTimeZone, + string toTimeZone) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ConvertTimeZone))); + + /// + /// Converts the `DateOnly?` value from the time zone given by to the time zone given by and returns the resulting value. + /// Corresponds to `CONVERT_TZ(dateTime, fromTimeZone, toTimeZone)`.. + /// + /// The DbFunctions instance. + /// The `DateOnly?` value to convert. + /// The time zone to convert from. + /// The time zone to convert to. + /// The converted value. + public static DateOnly? ConvertTimeZone( + [CanBeNull] this DbFunctions _, + DateOnly? dateOnly, + string fromTimeZone, + string toTimeZone) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ConvertTimeZone))); + + /// + /// Converts the `DateTime` value from `@@session.time_zone` to the time zone given by and returns the resulting value. + /// Corresponds to `CONVERT_TZ(dateTime, @@session.time_zone, toTimeZone)`. + /// + /// The DbFunctions instance. + /// The `DateTime` value to convert. + /// The time zone to convert to. + /// The converted value. + public static DateTime? ConvertTimeZone( + [CanBeNull] this DbFunctions _, + DateTime dateTime, + string toTimeZone) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ConvertTimeZone))); + + /// + /// Converts the `DateTimeOffset` value from `+00:00`/UTC to the time zone given by and returns the resulting value as a `DateTime`. + /// Corresponds to `CONVERT_TZ(dateTime, '+00:00', toTimeZone)`. + /// + /// The DbFunctions instance. + /// The `DateTimeOffset` value to convert. + /// The time zone to convert to. + /// The converted `DateTime?` value. + public static DateTime? ConvertTimeZone( + [CanBeNull] this DbFunctions _, + DateTimeOffset dateTimeOffset, + string toTimeZone) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ConvertTimeZone))); + + /// + /// Converts the `DateOnly` value from `@@session.time_zone` to the time zone given by and returns the resulting value. + /// Corresponds to `CONVERT_TZ(dateTime, @@session.time_zone, toTimeZone)`. + /// + /// The DbFunctions instance. + /// The `DateOnly` value to convert. + /// The time zone to convert to. + /// The converted value. + public static DateOnly? ConvertTimeZone( + [CanBeNull] this DbFunctions _, + DateOnly dateOnly, + string toTimeZone) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ConvertTimeZone))); + + /// + /// Converts the `DateTime?` value from `@@session.time_zone` to the time zone given by and returns the resulting value. + /// Corresponds to `CONVERT_TZ(dateTime, @@session.time_zone, toTimeZone)`. + /// + /// The DbFunctions instance. + /// The `DateTime?` value to convert. + /// The time zone to convert to. + /// The converted value. + public static DateTime? ConvertTimeZone( + [CanBeNull] this DbFunctions _, + DateTime? dateTime, + string toTimeZone) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ConvertTimeZone))); + + /// + /// Converts the `DateTimeOffset?` value from `+00:00`/UTC to the time zone given by and returns the resulting value as a `DateTime`. + /// Corresponds to `CONVERT_TZ(dateTime, '+00:00', toTimeZone)`. + /// + /// The DbFunctions instance. + /// The `DateTimeOffset?` value to convert. + /// The time zone to convert to. + /// The converted `DateTime?` value. + public static DateTime? ConvertTimeZone( + [CanBeNull] this DbFunctions _, + DateTimeOffset? dateTimeOffset, + string toTimeZone) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ConvertTimeZone))); + + /// + /// Converts the `DateOnly?` value from `@@session.time_zone` to the time zone given by and returns the resulting value. + /// Corresponds to `CONVERT_TZ(dateTime, @@session.time_zone, toTimeZone)`. + /// + /// The DbFunctions instance. + /// The `DateOnly?` value to convert. + /// The time zone to convert to. + /// The converted value. + public static DateOnly? ConvertTimeZone( + [CanBeNull] this DbFunctions _, + DateOnly? dateOnly, + string toTimeZone) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ConvertTimeZone))); + + #endregion ConvertTimeZone + /// /// Counts the number of year boundaries crossed between the startDate and endDate. /// Corresponds to TIMESTAMPDIFF(YEAR,startDate,endDate). diff --git a/src/EFCore.SingleStore/Extensions/SingleStorePropertyExtensions.cs b/src/EFCore.SingleStore/Extensions/SingleStorePropertyExtensions.cs index 3598c4781..b9cfa7b2a 100644 --- a/src/EFCore.SingleStore/Extensions/SingleStorePropertyExtensions.cs +++ b/src/EFCore.SingleStore/Extensions/SingleStorePropertyExtensions.cs @@ -65,7 +65,10 @@ public static SingleStoreValueGenerationStrategy GetValueGenerationStrategy([Not if (property.ValueGenerated == ValueGenerated.OnAddOrUpdate) { - if (IsCompatibleComputedColumn(property)) + // We explicitly check for RowVersion when generation migrations. We therefore handle RowVersion separately from other cases + // of using CURRENT_TIMESTAMP etc. and we don't generate a SingleStoreValueGenerationStrategy.ComputedColumn annotation. + if (IsCompatibleComputedColumn(property) && + !property.IsConcurrencyToken) { return SingleStoreValueGenerationStrategy.ComputedColumn; } @@ -145,7 +148,10 @@ is StoreObjectIdentifier principal if (property.ValueGenerated == ValueGenerated.OnAddOrUpdate) { - if (IsCompatibleComputedColumn(property)) + // We explicitly check for RowVersion when generation migrations. We therefore handle RowVersion separately from other cases + // of using CURRENT_TIMESTAMP etc. and we don't generate a SingleStoreValueGenerationStrategy.ComputedColumn annotation. + if (IsCompatibleComputedColumn(property, storeObject, typeMappingSource) && + !property.IsConcurrencyToken) { return SingleStoreValueGenerationStrategy.ComputedColumn; } @@ -422,20 +428,35 @@ private static bool IsCompatibleCurrentTimestampColumn( /// if compatible. public static bool IsCompatibleComputedColumn(IReadOnlyProperty property) { - var type = property.ClrType; + var valueConverter = GetConverter(property); + var type = (valueConverter?.ProviderClrType ?? property.ClrType).UnwrapNullableType(); // RowVersion uses byte[] and the BytesToDateTimeConverter. - return (type == typeof(DateTime) || type == typeof(DateTimeOffset)) && !HasConverter(property) - || type == typeof(byte[]) && !HasExternalConverter(property); + return type == typeof(DateTime) || + type == typeof(DateTimeOffset) || + type == typeof(byte[]) && valueConverter is BytesToDateTimeConverter; } - private static bool HasConverter(IReadOnlyProperty property) - => GetConverter(property) != null; - - private static bool HasExternalConverter(IReadOnlyProperty property) + private static bool IsCompatibleComputedColumn( + IReadOnlyProperty property, + in StoreObjectIdentifier storeObject, + ITypeMappingSource typeMappingSource) { - var converter = GetConverter(property); - return converter != null && !(converter is BytesToDateTimeConverter); + if (storeObject.StoreObjectType != StoreObjectType.Table) + { + return false; + } + + var valueConverter = property.GetValueConverter() ?? + (property.FindRelationalTypeMapping(storeObject) ?? + typeMappingSource?.FindMapping((IProperty)property))?.Converter; + + var type = (valueConverter?.ProviderClrType ?? property.ClrType).UnwrapNullableType(); + + // RowVersion uses byte[] and the BytesToDateTimeConverter. + return type == typeof(DateTime) || + type == typeof(DateTimeOffset) || + type == typeof(byte[]) && valueConverter is BytesToDateTimeConverter; } private static ValueConverter GetConverter(IReadOnlyProperty property) diff --git a/src/EFCore.SingleStore/Infrastructure/ServerVersionSupport.cs b/src/EFCore.SingleStore/Infrastructure/ServerVersionSupport.cs index 080c2eee3..52761f85e 100644 --- a/src/EFCore.SingleStore/Infrastructure/ServerVersionSupport.cs +++ b/src/EFCore.SingleStore/Infrastructure/ServerVersionSupport.cs @@ -96,7 +96,7 @@ public virtual bool PropertyOrVersion(string propertyNameOrServerVersion) public virtual bool JsonValue => false; public virtual bool Values => false; public virtual bool ValuesWithRows => false; - public virtual bool OffsetReferencesOuterQuery => false; + public virtual bool WhereSubqueryReferencesOuterQuery => false; public virtual bool JsonTableImplementationStable => JsonTable; public virtual bool JsonTableImplementationWithoutMySqlBugs => JsonTable; diff --git a/src/EFCore.SingleStore/Infrastructure/SingleStoreServerVersion.cs b/src/EFCore.SingleStore/Infrastructure/SingleStoreServerVersion.cs index 300b8126e..53b08c537 100644 --- a/src/EFCore.SingleStore/Infrastructure/SingleStoreServerVersion.cs +++ b/src/EFCore.SingleStore/Infrastructure/SingleStoreServerVersion.cs @@ -95,7 +95,7 @@ internal SingleStoreServerVersionSupport([NotNull] ServerVersion serverVersion) public override bool JsonValue => false; public override bool Values => false; public override bool ValuesWithRows => false; - public override bool OffsetReferencesOuterQuery => false; + public override bool WhereSubqueryReferencesOuterQuery => false; public override bool JsonTableImplementationStable => false; public override bool JsonTableImplementationWithoutMySqlBugs => false; // Other non-fatal bugs regarding JSON_TABLE. diff --git a/src/EFCore.SingleStore/Query/ExpressionTranslators/Internal/SingleStoreDbFunctionsExtensionsMethodTranslator.cs b/src/EFCore.SingleStore/Query/ExpressionTranslators/Internal/SingleStoreDbFunctionsExtensionsMethodTranslator.cs index de4ea223c..d614075f4 100644 --- a/src/EFCore.SingleStore/Query/ExpressionTranslators/Internal/SingleStoreDbFunctionsExtensionsMethodTranslator.cs +++ b/src/EFCore.SingleStore/Query/ExpressionTranslators/Internal/SingleStoreDbFunctionsExtensionsMethodTranslator.cs @@ -12,6 +12,7 @@ using Microsoft.EntityFrameworkCore.Query.SqlExpressions; using Microsoft.EntityFrameworkCore.Storage; using EntityFrameworkCore.SingleStore.Query.Internal; +using EntityFrameworkCore.SingleStore.Utilities; namespace EntityFrameworkCore.SingleStore.Query.ExpressionTranslators.Internal { @@ -23,6 +24,40 @@ public class SingleStoreDbFunctionsExtensionsMethodTranslator : IMethodCallTrans { private readonly SingleStoreSqlExpressionFactory _sqlExpressionFactory; + private static readonly HashSet _convertTimeZoneMethodInfos = + [ + typeof(SingleStoreDbFunctionsExtensions).GetRuntimeMethod( + nameof(SingleStoreDbFunctionsExtensions.ConvertTimeZone), + new[] { typeof(DbFunctions), typeof(DateTime), typeof(string), typeof(string) }), + typeof(SingleStoreDbFunctionsExtensions).GetRuntimeMethod( + nameof(SingleStoreDbFunctionsExtensions.ConvertTimeZone), + new[] { typeof(DbFunctions), typeof(DateOnly), typeof(string), typeof(string) }), + typeof(SingleStoreDbFunctionsExtensions).GetRuntimeMethod( + nameof(SingleStoreDbFunctionsExtensions.ConvertTimeZone), + new[] { typeof(DbFunctions), typeof(DateTime?), typeof(string), typeof(string) }), + typeof(SingleStoreDbFunctionsExtensions).GetRuntimeMethod( + nameof(SingleStoreDbFunctionsExtensions.ConvertTimeZone), + new[] { typeof(DbFunctions), typeof(DateOnly?), typeof(string), typeof(string) }), + typeof(SingleStoreDbFunctionsExtensions).GetRuntimeMethod( + nameof(SingleStoreDbFunctionsExtensions.ConvertTimeZone), + new[] { typeof(DbFunctions), typeof(DateTime), typeof(string) }), + typeof(SingleStoreDbFunctionsExtensions).GetRuntimeMethod( + nameof(SingleStoreDbFunctionsExtensions.ConvertTimeZone), + new[] { typeof(DbFunctions), typeof(DateTimeOffset), typeof(string) }), + typeof(SingleStoreDbFunctionsExtensions).GetRuntimeMethod( + nameof(SingleStoreDbFunctionsExtensions.ConvertTimeZone), + new[] { typeof(DbFunctions), typeof(DateOnly), typeof(string) }), + typeof(SingleStoreDbFunctionsExtensions).GetRuntimeMethod( + nameof(SingleStoreDbFunctionsExtensions.ConvertTimeZone), + new[] { typeof(DbFunctions), typeof(DateTime?), typeof(string) }), + typeof(SingleStoreDbFunctionsExtensions).GetRuntimeMethod( + nameof(SingleStoreDbFunctionsExtensions.ConvertTimeZone), + new[] { typeof(DbFunctions), typeof(DateTimeOffset?), typeof(string) }), + typeof(SingleStoreDbFunctionsExtensions).GetRuntimeMethod( + nameof(SingleStoreDbFunctionsExtensions.ConvertTimeZone), + new[] { typeof(DbFunctions), typeof(DateOnly?), typeof(string) }), + ]; + private static readonly Type[] _supportedLikeTypes = { typeof(int), typeof(long), @@ -149,6 +184,29 @@ public virtual SqlExpression Translate( IReadOnlyList arguments, IDiagnosticsLogger logger) { + if (_convertTimeZoneMethodInfos.TryGetValue(method, out _)) + { + // Will not just return `NULL` if any of its parameters is `NULL`, but also if `fromTimeZone` or `toTimeZone` is incorrect. + // Will do no conversion at all if `dateTime` is outside the supported range. + return _sqlExpressionFactory.NullableFunction( + "CONVERT_TZ", + arguments.Count == 3 + ? + [ + arguments[1], + // The implicit fromTimeZone is UTC for DateTimeOffset values and the current session time zone otherwise. + method.GetParameters()[1].ParameterType.UnwrapNullableType() == typeof(DateTimeOffset) + ? _sqlExpressionFactory.Constant("+00:00") + : _sqlExpressionFactory.Fragment("@@session.time_zone"), + arguments[2] + ] + : new[] { arguments[1], arguments[2], arguments[3] }, + method.ReturnType.UnwrapNullableType(), + null, + false, + Statics.GetTrueValues(arguments.Count)); + } + if (_likeMethodInfos.Any(m => Equals(method, m))) { var match = _sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[1]); diff --git a/src/EFCore.SingleStore/Query/ExpressionTranslators/Internal/SingleStoreStringComparisonMethodTranslator.cs b/src/EFCore.SingleStore/Query/ExpressionTranslators/Internal/SingleStoreStringComparisonMethodTranslator.cs index ebf5c15ef..262fd28bb 100644 --- a/src/EFCore.SingleStore/Query/ExpressionTranslators/Internal/SingleStoreStringComparisonMethodTranslator.cs +++ b/src/EFCore.SingleStore/Query/ExpressionTranslators/Internal/SingleStoreStringComparisonMethodTranslator.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -539,7 +540,9 @@ protected virtual SqlExpression GetLikeExpressionUsingParameter(QueryCompilation QueryCompilationContext.QueryContextParameter); var escapedPatternParameter = - queryCompilationContext.RegisterRuntimeParameter(patternParameter.Name + "_rewritten", lambda); + queryCompilationContext.RegisterRuntimeParameter( + $"{patternParameter.Name}_{methodType.ToString().ToLower(CultureInfo.InvariantCulture)}", + lambda); return _sqlExpressionFactory.Like( targetTransform(target), diff --git a/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreQuerySqlGenerator.cs b/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreQuerySqlGenerator.cs index d0d9fe593..dff9a70e4 100644 --- a/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreQuerySqlGenerator.cs +++ b/src/EFCore.SingleStore/Query/ExpressionVisitors/Internal/SingleStoreQuerySqlGenerator.cs @@ -555,6 +555,11 @@ protected override void GenerateValues(ValuesExpression valuesExpression) return; } + if (valuesExpression.RowValues.Count == 0) + { + throw new InvalidOperationException(RelationalStrings.EmptyCollectionNotSupportedAsInlineQueryRoot); + } + var rowValues = valuesExpression.RowValues; // @@ -865,28 +870,6 @@ public virtual Expression VisitSingleStoreBinaryExpression(SingleStoreBinaryExpr return mySqlBinaryExpression; } - protected override Expression VisitOrdering(OrderingExpression orderingExpression) - { - // The base implementation translates this case to `(SELECT 1)`. - // This leads to a bug in Oracle's MySQL, where completely wrong data is being returned under certain conditions. - // This can be reproduced by executing a no-op (or any existing) test of the `ProxyGraphUpdatesSingleStoreTest+LazyLoading` class (e.g. our own `DummyTest`), - // immediately followed by NorthwindSplitIncludeNoTrackingQuerySingleStoreTest.Include_collection_OrderBy_empty_list_contains(async: False). - // As a workaround, we just output `1` instead of `(SELECT 1)` for all database versions and types. - if (orderingExpression.Expression is SqlConstantExpression or SqlParameterExpression) - { - Sql.Append("1"); - - if (!orderingExpression.IsAscending) - { - Sql.Append(" DESC"); - } - - return orderingExpression; - } - - return base.VisitOrdering(orderingExpression); - } - protected virtual Expression VisitJsonTableExpression(SingleStoreJsonTableExpression jsonTableExpression) { // if (jsonTableExpression.ColumnInfos is not { Count: > 0 }) diff --git a/src/EFCore.SingleStore/Query/Internal/SingleStoreDateTimeMemberTranslator.cs b/src/EFCore.SingleStore/Query/Internal/SingleStoreDateTimeMemberTranslator.cs index 16682c245..1bdb2cbf5 100644 --- a/src/EFCore.SingleStore/Query/Internal/SingleStoreDateTimeMemberTranslator.cs +++ b/src/EFCore.SingleStore/Query/Internal/SingleStoreDateTimeMemberTranslator.cs @@ -9,6 +9,7 @@ using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using EntityFrameworkCore.SingleStore.Utilities; namespace EntityFrameworkCore.SingleStore.Query.Internal { @@ -142,6 +143,27 @@ public virtual SqlExpression Translate( } } + if (declaringType == typeof(DateTimeOffset)) + { + switch (member.Name) + { + case nameof(DateTimeOffset.DateTime): + case nameof(DateTimeOffset.UtcDateTime): + // We represent `DateTimeOffset` values as UTC datetime values in the database. Therefore, `DateTimeOffset`, + // `DateTimeOffset.DateTime` and `DateTimeOffset.UtcDateTime` are all the same. + return _sqlExpressionFactory.Convert(instance, typeof(DateTime)); + + case nameof(DateTimeOffset.LocalDateTime): + return _sqlExpressionFactory.NullableFunction( + "CONVERT_TZ", + [instance, _sqlExpressionFactory.Constant("+00:00"), _sqlExpressionFactory.Fragment("@@session.time_zone")], + typeof(DateTime), + null, + false, + Statics.GetTrueValues(3)); + } + } + return null; } } From 6863170c1836f2eb940099dd82afcfbd247a790c Mon Sep 17 00:00:00 2001 From: Olha Kramarenko Date: Mon, 19 Jan 2026 16:51:57 +0200 Subject: [PATCH 3/8] grab updates for FunctionalTests --- .../SingleStoreRelationalConnection.cs | 49 +++-- .../NorthwindBulkUpdatesSingleStoreTest.cs | 2 +- .../ConnectionSingleStoreTest.cs | 28 +++ .../EFCore.SingleStore.FunctionalTests.csproj | 18 +- ...MigrationsInfrastructureSingleStoreTest.cs | 55 +++++- .../ProxyGraphUpdatesSingleStoreTest.cs | 7 - .../Query/FunkyDataQuerySingleStoreTest.cs | 15 ++ ...rsOfWarQuerySingleStoreTest.SingleStore.cs | 34 ++++ ...dAggregateOperatorsQuerySingleStoreTest.cs | 20 ++ ...ndEFPropertyIncludeQuerySingleStoreTest.cs | 4 +- .../NorthwindFunctionsQuerySingleStoreTest.cs | 12 +- ...thwindMiscellaneousQuerySingleStoreTest.cs | 38 ++++ ...rthwindQueryFiltersQuerySingleStoreTest.cs | 4 +- ...rimitiveCollectionsQuerySingleStoreTest.cs | 175 ++++++++++++++---- .../TPCGearsOfWarQuerySingleStoreTest.cs | 4 +- 15 files changed, 380 insertions(+), 85 deletions(-) diff --git a/src/EFCore.SingleStore/Storage/Internal/SingleStoreRelationalConnection.cs b/src/EFCore.SingleStore/Storage/Internal/SingleStoreRelationalConnection.cs index 1c20ba5f0..d0299f250 100644 --- a/src/EFCore.SingleStore/Storage/Internal/SingleStoreRelationalConnection.cs +++ b/src/EFCore.SingleStore/Storage/Internal/SingleStoreRelationalConnection.cs @@ -90,7 +90,6 @@ protected static DbDataSource GetEffectiveDataSource(ISingleStoreOptions mySqlSi => mySqlSingletonOptions.DataSource ?? contextOptions.FindExtension()?.ApplicationServiceProvider?.GetService(); - // TODO: Remove, because we don't use it anywhere. private bool IsMasterConnection { get; set; } protected override DbConnection CreateDbConnection() @@ -178,14 +177,19 @@ public virtual ISingleStoreRelationalConnection CreateMasterConnection() optionsBuilderInfrastructure.AddOrUpdateExtension(masterMySqlOptions); - return new SingleStoreRelationalConnection( + return CreateMasterConnectionCore(optionsBuilder, _mySqlConnectionStringOptionsValidator); + } + + protected virtual ISingleStoreRelationalConnection CreateMasterConnectionCore( + DbContextOptionsBuilder optionsBuilder, + ISingleStoreConnectionStringOptionsValidator mySqlConnectionStringOptionsValidator) + => new SingleStoreRelationalConnection( Dependencies with { ContextOptions = optionsBuilder.Options }, - _mySqlConnectionStringOptionsValidator, + mySqlConnectionStringOptionsValidator, dataSource: null) { IsMasterConnection = true }; - } protected virtual SingleStoreConnectionStringBuilder AddConnectionStringOptions(SingleStoreConnectionStringBuilder builder) { @@ -255,7 +259,9 @@ public override bool Open(bool errorsExpected = false) if (result) { - if (_mySqlOptionsExtension.UpdateSqlModeOnOpen && _mySqlOptionsExtension.NoBackslashEscapes) + if (_mySqlOptionsExtension.UpdateSqlModeOnOpen && + _mySqlOptionsExtension.NoBackslashEscapes && + !IsMasterConnection) { AddSqlMode(NoBackslashEscapes); } @@ -271,9 +277,11 @@ public override async Task OpenAsync(CancellationToken cancellationToken, if (result) { - if (_mySqlOptionsExtension.UpdateSqlModeOnOpen && _mySqlOptionsExtension.NoBackslashEscapes) + if (_mySqlOptionsExtension.UpdateSqlModeOnOpen && + _mySqlOptionsExtension.NoBackslashEscapes && + !IsMasterConnection) { - await AddSqlModeAsync(NoBackslashEscapes) + await AddSqlModeAsync(NoBackslashEscapes, cancellationToken) .ConfigureAwait(false); } } @@ -282,15 +290,32 @@ await AddSqlModeAsync(NoBackslashEscapes) } public virtual void AddSqlMode(string mode) - => Dependencies.CurrentContext.Context?.Database.ExecuteSqlInterpolated($@"SET SESSION sql_mode = CONCAT(@@sql_mode, ',', {mode});"); + => ExecuteNonQuery($@"SET SESSION sql_mode = CONCAT(@@sql_mode, ',', '{mode}');"); public virtual Task AddSqlModeAsync(string mode, CancellationToken cancellationToken = default) - => Dependencies.CurrentContext.Context?.Database.ExecuteSqlInterpolatedAsync($@"SET SESSION sql_mode = CONCAT(@@sql_mode, ',', {mode});", cancellationToken); + => ExecuteNonQueryAsync($@"SET SESSION sql_mode = CONCAT(@@sql_mode, ',', '{mode}');", cancellationToken); public virtual void RemoveSqlMode(string mode) - => Dependencies.CurrentContext.Context?.Database.ExecuteSqlInterpolated($@"SET SESSION sql_mode = REPLACE(@@sql_mode, {mode}, '');"); + => ExecuteNonQuery($@"SET SESSION sql_mode = REPLACE(@@sql_mode, '{mode}', '');"); + + public virtual Task RemoveSqlModeAsync(string mode, CancellationToken cancellationToken = default) + => ExecuteNonQueryAsync($@"SET SESSION sql_mode = REPLACE(@@sql_mode, '{mode}', '');", cancellationToken); + + protected virtual void ExecuteNonQuery(string sql) + { + using var command = DbConnection.CreateCommand(); + command.CommandText = sql; + command.ExecuteNonQuery(); + } - public virtual void RemoveSqlModeAsync(string mode, CancellationToken cancellationToken = default) - => Dependencies.CurrentContext.Context?.Database.ExecuteSqlInterpolatedAsync($@"SET SESSION sql_mode = REPLACE(@@sql_mode, {mode}, '');", cancellationToken); + protected virtual async Task ExecuteNonQueryAsync(string sql, CancellationToken cancellationToken = default) + { + var command = DbConnection.CreateCommand(); + await using (command.ConfigureAwait(false)) + { + command.CommandText = sql; + await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false); + } + } } } diff --git a/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSingleStoreTest.cs index e4e652e5a..f627a14b5 100644 --- a/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSingleStoreTest.cs +++ b/test/EFCore.SingleStore.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSingleStoreTest.cs @@ -1042,7 +1042,7 @@ public override async Task Update_Where_set_property_plus_parameter(bool async) AssertExecuteUpdateSql( """ -@__value_0='Abc' (Size = 30) +@__value_0='Abc' (Size = 4000) UPDATE `Customers` AS `c` SET `c`.`ContactName` = CONCAT(COALESCE(`c`.`ContactName`, ''), @__value_0) diff --git a/test/EFCore.SingleStore.FunctionalTests/ConnectionSingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/ConnectionSingleStoreTest.cs index 3ecc2551b..82085471b 100644 --- a/test/EFCore.SingleStore.FunctionalTests/ConnectionSingleStoreTest.cs +++ b/test/EFCore.SingleStore.FunctionalTests/ConnectionSingleStoreTest.cs @@ -4,6 +4,7 @@ using System.Reflection; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.DependencyInjection; using SingleStoreConnector; using EntityFrameworkCore.SingleStore.FunctionalTests.TestUtilities; @@ -169,6 +170,33 @@ public void Can_create_admin_connection_with_connection() masterConnection.Open(); } + + [Fact] + public void Can_create_database_with_disablebackslashescaping() + { + var optionsBuilder = new DbContextOptionsBuilder(); + optionsBuilder.UseSingleStore(SingleStoreTestStore.CreateConnectionString("ConnectionTest_" + Guid.NewGuid()), b => b.ApplyConfiguration().DisableBackslashEscaping()); + using var context = new GeneralOptionsContext(optionsBuilder.Options); + + var relationalDatabaseCreator = context.GetService(); + + try + { + relationalDatabaseCreator.EnsureCreated(); + } + finally + { + try + { + relationalDatabaseCreator.EnsureDeleted(); + } + catch + { + // ignored + } + } + } + private readonly IServiceProvider _serviceProvider = new ServiceCollection() .AddEntityFrameworkSingleStore() .BuildServiceProvider(); diff --git a/test/EFCore.SingleStore.FunctionalTests/EFCore.SingleStore.FunctionalTests.csproj b/test/EFCore.SingleStore.FunctionalTests/EFCore.SingleStore.FunctionalTests.csproj index 9d0e96969..453bdacbe 100644 --- a/test/EFCore.SingleStore.FunctionalTests/EFCore.SingleStore.FunctionalTests.csproj +++ b/test/EFCore.SingleStore.FunctionalTests/EFCore.SingleStore.FunctionalTests.csproj @@ -12,7 +12,7 @@ false false - + $(MSBuildWarningsAsMessages);$(NoWarn) $(DefineConstants);FIXED_TEST_ORDER @@ -44,28 +44,28 @@ - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\Debug\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.dll - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\Debug\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Abstractions.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Abstractions.dll - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\Debug\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Analyzers.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Analyzers.dll - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\Debug\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Proxies.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Proxies.dll - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\Debug\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Relational.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Relational.dll - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\Debug\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Relational.Specification.Tests.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Relational.Specification.Tests.dll - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\Debug\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Specification.Tests.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Specification.Tests.dll - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Design.Tests\Debug\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Design.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Design.Tests\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Design.dll diff --git a/test/EFCore.SingleStore.FunctionalTests/MigrationsInfrastructureSingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/MigrationsInfrastructureSingleStoreTest.cs index f8530e54e..c0a05dba7 100644 --- a/test/EFCore.SingleStore.FunctionalTests/MigrationsInfrastructureSingleStoreTest.cs +++ b/test/EFCore.SingleStore.FunctionalTests/MigrationsInfrastructureSingleStoreTest.cs @@ -55,7 +55,8 @@ public override void Can_generate_up_scripts() base.Can_generate_up_scripts(); Assert.Equal( - @"CREATE TABLE IF NOT EXISTS `__EFMigrationsHistory` ( +""" +CREATE TABLE IF NOT EXISTS `__EFMigrationsHistory` ( `MigrationId` varchar(150) CHARACTER SET utf8mb4 NOT NULL, `ProductVersion` varchar(32) CHARACTER SET utf8mb4 NOT NULL, CONSTRAINT `PK___EFMigrationsHistory` PRIMARY KEY (`MigrationId`) @@ -90,7 +91,7 @@ public override void Can_generate_up_scripts() COMMIT; -", +""", Sql, ignoreLineEndingDifferences: true); } @@ -109,7 +110,11 @@ public override void Can_generate_one_up_script() COMMIT; -", +START TRANSACTION; +INSERT INTO `__EFMigrationsHistory` (`MigrationId`, `ProductVersion`) +VALUES ('00000000000004_Migration4', '7.0.0-test'); +COMMIT; +""", Sql, ignoreLineEndingDifferences: true); } @@ -137,7 +142,9 @@ public override void Can_generate_idempotent_up_scripts() { base.Can_generate_idempotent_up_scripts(); - Assert.Equal(@"CREATE TABLE IF NOT EXISTS `__EFMigrationsHistory` ( + Assert.Equal( +""" +CREATE TABLE IF NOT EXISTS `__EFMigrationsHistory` ( `MigrationId` varchar(150) CHARACTER SET utf8mb4 NOT NULL, `ProductVersion` varchar(32) CHARACTER SET utf8mb4 NOT NULL, CONSTRAINT `PK___EFMigrationsHistory` PRIMARY KEY (`MigrationId`) @@ -232,7 +239,21 @@ IF NOT EXISTS(SELECT 1 FROM `__EFMigrationsHistory` WHERE `MigrationId` = '00000 COMMIT; -", +START TRANSACTION; +DROP PROCEDURE IF EXISTS MigrationsScript; +DELIMITER // +CREATE PROCEDURE MigrationsScript() +BEGIN + IF NOT EXISTS(SELECT 1 FROM `__EFMigrationsHistory` WHERE `MigrationId` = '00000000000004_Migration4') THEN + INSERT INTO `__EFMigrationsHistory` (`MigrationId`, `ProductVersion`) + VALUES ('00000000000004_Migration4', '7.0.0-test'); + END IF; +END // +DELIMITER ; +CALL MigrationsScript(); +DROP PROCEDURE MigrationsScript; +COMMIT; +""", Sql, ignoreLineEndingDifferences: true); } @@ -391,7 +412,8 @@ public override void Can_generate_up_scripts_noTransactions() base.Can_generate_up_scripts_noTransactions(); Assert.Equal( - @"CREATE TABLE IF NOT EXISTS `__EFMigrationsHistory` ( +""" +CREATE TABLE IF NOT EXISTS `__EFMigrationsHistory` ( `MigrationId` varchar(150) CHARACTER SET utf8mb4 NOT NULL, `ProductVersion` varchar(32) CHARACTER SET utf8mb4 NOT NULL, CONSTRAINT `PK___EFMigrationsHistory` PRIMARY KEY (`MigrationId`) @@ -414,7 +436,9 @@ public override void Can_generate_up_scripts_noTransactions() INSERT INTO `__EFMigrationsHistory` (`MigrationId`, `ProductVersion`) VALUES ('00000000000003_Migration3', '7.0.0-test'); -", +INSERT INTO `__EFMigrationsHistory` (`MigrationId`, `ProductVersion`) +VALUES ('00000000000004_Migration4', '7.0.0-test'); +""", Sql, ignoreLineEndingDifferences: true); } @@ -424,7 +448,8 @@ public override void Can_generate_idempotent_up_scripts_noTransactions() base.Can_generate_idempotent_up_scripts_noTransactions(); Assert.Equal( - @"CREATE TABLE IF NOT EXISTS `__EFMigrationsHistory` ( +""" +CREATE TABLE IF NOT EXISTS `__EFMigrationsHistory` ( `MigrationId` varchar(150) CHARACTER SET utf8mb4 NOT NULL, `ProductVersion` varchar(32) CHARACTER SET utf8mb4 NOT NULL, CONSTRAINT `PK___EFMigrationsHistory` PRIMARY KEY (`MigrationId`) @@ -507,7 +532,19 @@ IF NOT EXISTS(SELECT 1 FROM `__EFMigrationsHistory` WHERE `MigrationId` = '00000 CALL MigrationsScript(); DROP PROCEDURE MigrationsScript; -", +DROP PROCEDURE IF EXISTS MigrationsScript; +DELIMITER // +CREATE PROCEDURE MigrationsScript() +BEGIN + IF NOT EXISTS(SELECT 1 FROM `__EFMigrationsHistory` WHERE `MigrationId` = '00000000000004_Migration4') THEN + INSERT INTO `__EFMigrationsHistory` (`MigrationId`, `ProductVersion`) + VALUES ('00000000000004_Migration4', '7.0.0-test'); + END IF; +END // +DELIMITER ; +CALL MigrationsScript(); +DROP PROCEDURE MigrationsScript; +""", Sql, ignoreLineEndingDifferences: true); } diff --git a/test/EFCore.SingleStore.FunctionalTests/ProxyGraphUpdatesSingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/ProxyGraphUpdatesSingleStoreTest.cs index edab7cad4..8c78cd258 100644 --- a/test/EFCore.SingleStore.FunctionalTests/ProxyGraphUpdatesSingleStoreTest.cs +++ b/test/EFCore.SingleStore.FunctionalTests/ProxyGraphUpdatesSingleStoreTest.cs @@ -4,7 +4,6 @@ using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.Extensions.DependencyInjection; using EntityFrameworkCore.SingleStore.FunctionalTests.TestUtilities; -using Xunit; namespace EntityFrameworkCore.SingleStore.FunctionalTests { @@ -39,12 +38,6 @@ public LazyLoading(ProxyGraphUpdatesWithLazyLoadingSingleStoreFixture fixture) { } - // Used to track down a bug in Oracle's MySQL implementation, related to `SELECT ... ORDER BY (SELECT 1)`. - [Fact] - public void DummyTest() - { - } - protected override bool DoesLazyLoading => true; diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/FunkyDataQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/FunkyDataQuerySingleStoreTest.cs index 391f09868..37baec85c 100644 --- a/test/EFCore.SingleStore.FunctionalTests/Query/FunkyDataQuerySingleStoreTest.cs +++ b/test/EFCore.SingleStore.FunctionalTests/Query/FunkyDataQuerySingleStoreTest.cs @@ -175,6 +175,21 @@ WHERE FALSE """); } + public override async Task String_Contains_and_StartsWith_with_same_parameter(bool async) + { + await base.String_Contains_and_StartsWith_with_same_parameter(async); + + AssertSql( + """ + @__s_0_contains='%B%' (Size = 4000) + @__s_0_startswith='B%' (Size = 4000) + SELECT `f`.`Id`, `f`.`FirstName`, `f`.`LastName`, `f`.`NullableBool` + FROM `FunkyCustomers` AS `f` + WHERE (`f`.`FirstName` LIKE @__s_0_contains) OR (`f`.`LastName` LIKE @__s_0_startswith) + """); + } + + protected override void ClearLog() => Fixture.TestSqlLoggerFactory.Clear(); diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/GearsOfWarQuerySingleStoreTest.SingleStore.cs b/test/EFCore.SingleStore.FunctionalTests/Query/GearsOfWarQuerySingleStoreTest.SingleStore.cs index 85aef8d45..4b2e0be4c 100644 --- a/test/EFCore.SingleStore.FunctionalTests/Query/GearsOfWarQuerySingleStoreTest.SingleStore.cs +++ b/test/EFCore.SingleStore.FunctionalTests/Query/GearsOfWarQuerySingleStoreTest.SingleStore.cs @@ -196,5 +196,39 @@ await AssertQuery( WHERE `w`.`IsAutomatic` = TRUE"), keys); // Breaking change in 5.0 due to bool expression optimization in `SqlNullabilityProcessor`. // Was "`w`.`IsAutomatic` <> FALSE" before. } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task DateTimeOffset_DateTime(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(e => e.Timeline == e.Timeline.DateTime), + ss => ss.Set().Where(e => true)); + + AssertSql( + """ + SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` + FROM `Missions` AS `m` + WHERE `m`.`Timeline` = `m`.`Timeline` + """); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task DateTimeOffset_UtcDateTime(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(e => e.Timeline == e.Timeline.UtcDateTime), + ss => ss.Set().Where(e => true)); + + AssertSql( + """ + SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline` + FROM `Missions` AS `m` + WHERE `m`.`Timeline` = `m`.`Timeline` + """); + } } } diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySingleStoreTest.cs index 9a67ff55e..2c209e44d 100644 --- a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySingleStoreTest.cs +++ b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySingleStoreTest.cs @@ -162,6 +162,26 @@ public override Task Sum_over_subquery_is_client_eval(bool async) return base.Sum_over_subquery_is_client_eval(async); } + public override async Task Contains_inside_Average_without_GroupBy(bool async) + { + var cities = new[] { "London", "Berlin" }; + + await AssertAverage( + async, + ss => ss.Set(), + selector: c => cities.Contains(c.City) ? 1.0 : 0.0, + asserter: (e, a) => Assert.Equal(e, a, 0.00001)); // expected: 0.076923076923076927, MySQL actual: 0.076920000000000002 + + AssertSql( + """ + SELECT AVG(CASE + WHEN `c`.`City` IN ('London', 'Berlin') THEN 1.0 + ELSE 0.0 + END) + FROM `Customers` AS `c` + """); + } + protected override bool CanExecuteQueryString => true; diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindEFPropertyIncludeQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindEFPropertyIncludeQuerySingleStoreTest.cs index fd1ca8390..b52dc5a60 100644 --- a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindEFPropertyIncludeQuerySingleStoreTest.cs +++ b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindEFPropertyIncludeQuerySingleStoreTest.cs @@ -1080,7 +1080,7 @@ LIMIT 18446744073709551610 OFFSET @__p_1 SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, FALSE AS `c` FROM `Customers` AS `c` WHERE `c`.`CustomerID` LIKE 'A%' - ORDER BY 1 + ORDER BY (SELECT 1) LIMIT 18446744073709551610 OFFSET @__p_1 ) AS `t` LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID` @@ -2009,7 +2009,7 @@ LIMIT 18446744073709551610 OFFSET @__p_1 SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, TRUE AS `c` FROM `Customers` AS `c` WHERE `c`.`CustomerID` LIKE 'A%' - ORDER BY 1 + ORDER BY (SELECT 1) LIMIT 18446744073709551610 OFFSET @__p_1 ) AS `t` LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID` diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindFunctionsQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindFunctionsQuerySingleStoreTest.cs index 65616342a..eef5342a3 100644 --- a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindFunctionsQuerySingleStoreTest.cs +++ b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindFunctionsQuerySingleStoreTest.cs @@ -535,11 +535,11 @@ public override async Task String_Contains_parameter_with_whitespace(bool async) AssertSql( """ -@__pattern_0_rewritten='% %' (Size = 30) +@__pattern_0_contains='% %' (Size = 30) SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region` FROM `Customers` AS `c` -WHERE `c`.`ContactName` LIKE @__pattern_0_rewritten +WHERE `c`.`ContactName` LIKE @__pattern_0_contains """); } @@ -1813,11 +1813,11 @@ public override async Task String_StartsWith_Parameter(bool async) AssertSql( """ -@__pattern_0_rewritten='M%' (Size = 30) +@__pattern_0_startswith='M%' (Size = 30) SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region` FROM `Customers` AS `c` -WHERE `c`.`ContactName` LIKE @__pattern_0_rewritten +WHERE `c`.`ContactName` LIKE @__pattern_0_startswith """); } @@ -1827,11 +1827,11 @@ public override async Task String_EndsWith_Parameter(bool async) AssertSql( """ -@__pattern_0_rewritten='%b' (Size = 30) +@__pattern_0_endswith='%b' (Size = 30) SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region` FROM `Customers` AS `c` -WHERE `c`.`ContactName` LIKE @__pattern_0_rewritten +WHERE `c`.`ContactName` LIKE @__pattern_0_endswith """); } diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindMiscellaneousQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindMiscellaneousQuerySingleStoreTest.cs index f0e781070..579c15ae4 100644 --- a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindMiscellaneousQuerySingleStoreTest.cs +++ b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindMiscellaneousQuerySingleStoreTest.cs @@ -730,6 +730,44 @@ public override Task DefaultIfEmpty_Sum_over_collection_navigation(bool async) return base.DefaultIfEmpty_Sum_over_collection_navigation(async); } + public override async Task Parameter_collection_Contains_with_projection_and_ordering(bool async) + { +#if EFCORE_DEBUG_BUILD + // GroupBy debug assert. Issue #26104. + Assert.StartsWith( + "Missing alias in the list", + (await Assert.ThrowsAsync( + () => base.Parameter_collection_Contains_with_projection_and_ordering(async))).Message); +#else + await base.Parameter_collection_Contains_with_projection_and_ordering(async); + + AssertSql( + """ + SELECT `o`.`Quantity` AS `Key`, ( + SELECT MAX(`o3`.`OrderDate`) + FROM `Order Details` AS `o2` + INNER JOIN `Orders` AS `o3` ON `o2`.`OrderID` = `o3`.`OrderID` + WHERE `o2`.`OrderID` IN (10248, 10249) AND (`o`.`Quantity` = `o2`.`Quantity`)) AS `MaxTimestamp` + FROM `Order Details` AS `o` + WHERE `o`.`OrderID` IN (10248, 10249) + GROUP BY `o`.`Quantity` + ORDER BY ( + SELECT MAX(`o3`.`OrderDate`) + FROM `Order Details` AS `o2` + INNER JOIN `Orders` AS `o3` ON `o2`.`OrderID` = `o3`.`OrderID` + WHERE `o2`.`OrderID` IN (10248, 10249) AND (`o`.`Quantity` = `o2`.`Quantity`)) + """); +#endif + } + + [SupportedServerVersionCondition(nameof(ServerVersionSupport.WhereSubqueryReferencesOuterQuery))] + public override async Task Subquery_with_navigation_inside_inline_collection(bool async) + { + await base.Subquery_with_navigation_inside_inline_collection(async); + + AssertSql(""); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindQueryFiltersQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindQueryFiltersQuerySingleStoreTest.cs index a6be3a11a..975524c0f 100644 --- a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindQueryFiltersQuerySingleStoreTest.cs +++ b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindQueryFiltersQuerySingleStoreTest.cs @@ -23,11 +23,11 @@ public override async Task Count_query(bool async) AssertSql( """ -@__ef_filter__TenantPrefix_0_rewritten='B%' (Size = 40) +@__ef_filter__TenantPrefix_0_startswith='B%' (Size = 40) SELECT COUNT(*) FROM `Customers` AS `c` -WHERE `c`.`CompanyName` LIKE @__ef_filter__TenantPrefix_0_rewritten +WHERE `c`.`CompanyName` LIKE @__ef_filter__TenantPrefix_0_startswith """); } diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/PrimitiveCollectionsQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/PrimitiveCollectionsQuerySingleStoreTest.cs index e5b5bbb98..4dd921e0f 100644 --- a/test/EFCore.SingleStore.FunctionalTests/Query/PrimitiveCollectionsQuerySingleStoreTest.cs +++ b/test/EFCore.SingleStore.FunctionalTests/Query/PrimitiveCollectionsQuerySingleStoreTest.cs @@ -61,10 +61,12 @@ public override async Task Inline_collection_of_nullable_ints_Contains_null(bool """); } - public override Task Inline_collection_Count_with_zero_values(bool async) - => AssertTranslationFailedWithDetails( - () => base.Inline_collection_Count_with_zero_values(async), - RelationalStrings.EmptyCollectionNotSupportedAsInlineQueryRoot); + public override async Task Inline_collection_Count_with_zero_values(bool async) + { + await base.Inline_collection_Count_with_zero_values(async); + + AssertSql(); + } public override async Task Inline_collection_Count_with_one_value(bool async) { @@ -148,10 +150,17 @@ SELECT COUNT(*) } } - public override Task Inline_collection_Contains_with_zero_values(bool async) - => AssertTranslationFailedWithDetails( - () => base.Inline_collection_Contains_with_zero_values(async), - RelationalStrings.EmptyCollectionNotSupportedAsInlineQueryRoot); + public override async Task Inline_collection_Contains_with_zero_values(bool async) + { + await base.Inline_collection_Contains_with_zero_values(async); + + AssertSql( + """ + SELECT `p`.`Id`, `p`.`Bool`, `p`.`Bools`, `p`.`DateTime`, `p`.`DateTimes`, `p`.`Enum`, `p`.`Enums`, `p`.`Int`, `p`.`Ints`, `p`.`NullableInt`, `p`.`NullableInts`, `p`.`NullableString`, `p`.`NullableStrings`, `p`.`String`, `p`.`Strings` + FROM `PrimitiveCollectionsEntity` AS `p` + WHERE FALSE + """); + } public override async Task Inline_collection_Contains_with_one_value(bool async) { @@ -259,9 +268,9 @@ FROM JSON_TABLE('[2,999]', '$[*]' COLUMNS ( """); } - public override async Task Parameter_collection_of_ints_Contains(bool async) + public override async Task Parameter_collection_of_ints_Contains_int(bool async) { - await base.Parameter_collection_of_ints_Contains(async); + await base.Parameter_collection_of_ints_Contains_int(async); if (SingleStoreTestHelpers.HasPrimitiveCollectionsSupport(Fixture)) { @@ -285,13 +294,19 @@ FROM JSON_TABLE('[10,999]', '$[*]' COLUMNS ( SELECT `p`.`Id`, `p`.`Bool`, `p`.`Bools`, `p`.`DateTime`, `p`.`DateTimes`, `p`.`Enum`, `p`.`Enums`, `p`.`Int`, `p`.`Ints`, `p`.`NullableInt`, `p`.`NullableInts`, `p`.`NullableString`, `p`.`NullableStrings`, `p`.`String`, `p`.`Strings` FROM `PrimitiveCollectionsEntity` AS `p` WHERE `p`.`Int` IN (10, 999) +""", + // + """ +SELECT `p`.`Id`, `p`.`Bool`, `p`.`Bools`, `p`.`DateTime`, `p`.`DateTimes`, `p`.`Enum`, `p`.`Enums`, `p`.`Int`, `p`.`Ints`, `p`.`NullableInt`, `p`.`NullableInts`, `p`.`NullableString`, `p`.`NullableStrings`, `p`.`String`, `p`.`Strings` +FROM `PrimitiveCollectionsEntity` AS `p` +WHERE `p`.`Int` NOT IN (10, 999) """); } } - public override async Task Parameter_collection_of_nullable_ints_Contains_int(bool async) + public override async Task Parameter_collection_of_ints_Contains_nullable_int(bool async) { - await base.Parameter_collection_of_nullable_ints_Contains_int(async); + await base.Parameter_collection_of_ints_Contains_nullable_int(async); if (SingleStoreTestHelpers.HasPrimitiveCollectionsSupport(Fixture)) { @@ -314,14 +329,20 @@ FROM JSON_TABLE('[10,999]', '$[*]' COLUMNS ( """ SELECT `p`.`Id`, `p`.`Bool`, `p`.`Bools`, `p`.`DateTime`, `p`.`DateTimes`, `p`.`Enum`, `p`.`Enums`, `p`.`Int`, `p`.`Ints`, `p`.`NullableInt`, `p`.`NullableInts`, `p`.`NullableString`, `p`.`NullableStrings`, `p`.`String`, `p`.`Strings` FROM `PrimitiveCollectionsEntity` AS `p` -WHERE `p`.`Int` IN (10, 999) +WHERE `p`.`NullableInt` IN (10, 999) +""", + // + """ +SELECT `p`.`Id`, `p`.`Bool`, `p`.`Bools`, `p`.`DateTime`, `p`.`DateTimes`, `p`.`Enum`, `p`.`Enums`, `p`.`Int`, `p`.`Ints`, `p`.`NullableInt`, `p`.`NullableInts`, `p`.`NullableString`, `p`.`NullableStrings`, `p`.`String`, `p`.`Strings` +FROM `PrimitiveCollectionsEntity` AS `p` +WHERE `p`.`NullableInt` NOT IN (10, 999) OR (`p`.`NullableInt` IS NULL) """); } } - public override async Task Parameter_collection_of_nullable_ints_Contains_nullable_int(bool async) + public override async Task Parameter_collection_of_nullable_ints_Contains_int(bool async) { - await base.Parameter_collection_of_nullable_ints_Contains_nullable_int(async); + await base.Parameter_collection_of_nullable_ints_Contains_int(async); if (SingleStoreTestHelpers.HasPrimitiveCollectionsSupport(Fixture)) { @@ -329,13 +350,13 @@ public override async Task Parameter_collection_of_nullable_ints_Contains_nullab """ SELECT `p`.`Id`, `p`.`Bool`, `p`.`Bools`, `p`.`DateTime`, `p`.`DateTimes`, `p`.`Enum`, `p`.`Enums`, `p`.`Int`, `p`.`Ints`, `p`.`NullableInt`, `p`.`NullableInts`, `p`.`NullableString`, `p`.`NullableStrings`, `p`.`String`, `p`.`Strings` FROM `PrimitiveCollectionsEntity` AS `p` -WHERE EXISTS ( - SELECT 1 - FROM JSON_TABLE('[null,999]', '$[*]' COLUMNS ( +WHERE `p`.`Int` IN ( + SELECT `n`.`value` + FROM JSON_TABLE('[10,999]', '$[*]' COLUMNS ( `key` FOR ORDINALITY, `value` int PATH '$[0]' )) AS `n` - WHERE (`n`.`value` = `p`.`NullableInt`) OR (`n`.`value` IS NULL AND (`p`.`NullableInt` IS NULL))) +) """); } else @@ -344,14 +365,20 @@ FROM JSON_TABLE('[null,999]', '$[*]' COLUMNS ( """ SELECT `p`.`Id`, `p`.`Bool`, `p`.`Bools`, `p`.`DateTime`, `p`.`DateTimes`, `p`.`Enum`, `p`.`Enums`, `p`.`Int`, `p`.`Ints`, `p`.`NullableInt`, `p`.`NullableInts`, `p`.`NullableString`, `p`.`NullableStrings`, `p`.`String`, `p`.`Strings` FROM `PrimitiveCollectionsEntity` AS `p` -WHERE `p`.`NullableInt` IS NULL OR (`p`.`NullableInt` = 999) +WHERE `p`.`Int` IN (10, 999) +""", + // + """ +SELECT `p`.`Id`, `p`.`Bool`, `p`.`Bools`, `p`.`DateTime`, `p`.`DateTimes`, `p`.`Enum`, `p`.`Enums`, `p`.`Int`, `p`.`Ints`, `p`.`NullableInt`, `p`.`NullableInts`, `p`.`NullableString`, `p`.`NullableStrings`, `p`.`String`, `p`.`Strings` +FROM `PrimitiveCollectionsEntity` AS `p` +WHERE `p`.`Int` NOT IN (10, 999) """); } } - public override async Task Parameter_collection_of_strings_Contains_nullable_string(bool async) + public override async Task Parameter_collection_of_nullable_ints_Contains_nullable_int(bool async) { - await base.Parameter_collection_of_strings_Contains_nullable_string(async); + await base.Parameter_collection_of_nullable_ints_Contains_nullable_int(async); if (SingleStoreTestHelpers.HasPrimitiveCollectionsSupport(Fixture)) { @@ -361,11 +388,11 @@ public override async Task Parameter_collection_of_strings_Contains_nullable_str FROM `PrimitiveCollectionsEntity` AS `p` WHERE EXISTS ( SELECT 1 - FROM JSON_TABLE('["999",null]', '$[*]' COLUMNS ( + FROM JSON_TABLE('[null,999]', '$[*]' COLUMNS ( `key` FOR ORDINALITY, - `value` longtext PATH '$[0]' - )) AS `s` - WHERE (`s`.`value` = `p`.`NullableString`) OR (`s`.`value` IS NULL AND (`p`.`NullableString` IS NULL))) + `value` int PATH '$[0]' + )) AS `n` + WHERE (`n`.`value` = `p`.`NullableInt`) OR (`n`.`value` IS NULL AND (`p`.`NullableInt` IS NULL))) """); } else @@ -374,14 +401,20 @@ FROM JSON_TABLE('["999",null]', '$[*]' COLUMNS ( """ SELECT `p`.`Id`, `p`.`Bool`, `p`.`Bools`, `p`.`DateTime`, `p`.`DateTimes`, `p`.`Enum`, `p`.`Enums`, `p`.`Int`, `p`.`Ints`, `p`.`NullableInt`, `p`.`NullableInts`, `p`.`NullableString`, `p`.`NullableStrings`, `p`.`String`, `p`.`Strings` FROM `PrimitiveCollectionsEntity` AS `p` -WHERE `p`.`NullableString` IS NULL OR (`p`.`NullableString` = '999') +WHERE `p`.`NullableInt` IS NULL OR (`p`.`NullableInt` = 999) +""", + // + """ +SELECT `p`.`Id`, `p`.`Bool`, `p`.`Bools`, `p`.`DateTime`, `p`.`DateTimes`, `p`.`Enum`, `p`.`Enums`, `p`.`Int`, `p`.`Ints`, `p`.`NullableInt`, `p`.`NullableInts`, `p`.`NullableString`, `p`.`NullableStrings`, `p`.`String`, `p`.`Strings` +FROM `PrimitiveCollectionsEntity` AS `p` +WHERE `p`.`NullableInt` IS NOT NULL AND (`p`.`NullableInt` <> 999) """); } } - public override async Task Parameter_collection_of_strings_Contains_non_nullable_string(bool async) + public override async Task Parameter_collection_of_strings_Contains_nullable_string(bool async) { - await base.Parameter_collection_of_strings_Contains_non_nullable_string(async); + await base.Parameter_collection_of_strings_Contains_nullable_string(async); if (SingleStoreTestHelpers.HasPrimitiveCollectionsSupport(Fixture)) { @@ -389,13 +422,13 @@ public override async Task Parameter_collection_of_strings_Contains_non_nullable """ SELECT `p`.`Id`, `p`.`Bool`, `p`.`Bools`, `p`.`DateTime`, `p`.`DateTimes`, `p`.`Enum`, `p`.`Enums`, `p`.`Int`, `p`.`Ints`, `p`.`NullableInt`, `p`.`NullableInts`, `p`.`NullableString`, `p`.`NullableStrings`, `p`.`String`, `p`.`Strings` FROM `PrimitiveCollectionsEntity` AS `p` -WHERE `p`.`String` IN ( - SELECT `s`.`value` - FROM JSON_TABLE('["10","999"]', '$[*]' COLUMNS ( +WHERE EXISTS ( + SELECT 1 + FROM JSON_TABLE('["999",null]', '$[*]' COLUMNS ( `key` FOR ORDINALITY, `value` longtext PATH '$[0]' )) AS `s` -) +WHERE (`s`.`value` = `p`.`NullableString`) OR (`s`.`value` IS NULL AND (`p`.`NullableString` IS NULL))) """); } else @@ -404,7 +437,13 @@ FROM JSON_TABLE('["10","999"]', '$[*]' COLUMNS ( """ SELECT `p`.`Id`, `p`.`Bool`, `p`.`Bools`, `p`.`DateTime`, `p`.`DateTimes`, `p`.`Enum`, `p`.`Enums`, `p`.`Int`, `p`.`Ints`, `p`.`NullableInt`, `p`.`NullableInts`, `p`.`NullableString`, `p`.`NullableStrings`, `p`.`String`, `p`.`Strings` FROM `PrimitiveCollectionsEntity` AS `p` -WHERE `p`.`String` IN ('10', '999') +WHERE `p`.`NullableString` IN ('10', '999') +""", + // + """ +SELECT `p`.`Id`, `p`.`Bool`, `p`.`Bools`, `p`.`DateTime`, `p`.`DateTimes`, `p`.`Enum`, `p`.`Enums`, `p`.`Int`, `p`.`Ints`, `p`.`NullableInt`, `p`.`NullableInts`, `p`.`NullableString`, `p`.`NullableStrings`, `p`.`String`, `p`.`Strings` +FROM `PrimitiveCollectionsEntity` AS `p` +WHERE `p`.`NullableString` NOT IN ('10', '999') OR (`p`.`NullableString` IS NULL) """); } } @@ -791,7 +830,7 @@ public override async Task Non_nullable_reference_column_collection_index_equals } } - [SupportedServerVersionCondition(nameof(ServerVersionSupport.OffsetReferencesOuterQuery))] + [SupportedServerVersionCondition(nameof(ServerVersionSupport.WhereSubqueryReferencesOuterQuery))] public override async Task Inline_collection_index_Column(bool async) { await base.Inline_collection_index_Column(async); @@ -1682,6 +1721,72 @@ END IN ('one', 'two', 'three') """); } + public override async Task Inline_collection_Contains_with_EF_Constant(bool async) + { + await base.Inline_collection_Contains_with_EF_Constant(async); + + AssertSql( +""" +SELECT `p`.`Id`, `p`.`Bool`, `p`.`Bools`, `p`.`DateTime`, `p`.`DateTimes`, `p`.`Enum`, `p`.`Enums`, `p`.`Int`, `p`.`Ints`, `p`.`NullableInt`, `p`.`NullableInts`, `p`.`NullableString`, `p`.`NullableStrings`, `p`.`String`, `p`.`Strings` +FROM `PrimitiveCollectionsEntity` AS `p` +WHERE `p`.`Id` IN (2, 999, 1000) +"""); + } + + public override async Task Parameter_collection_of_strings_Contains_string(bool async) + { + await base.Parameter_collection_of_strings_Contains_string(async); + + AssertSql( +""" +SELECT `p`.`Id`, `p`.`Bool`, `p`.`Bools`, `p`.`DateTime`, `p`.`DateTimes`, `p`.`Enum`, `p`.`Enums`, `p`.`Int`, `p`.`Ints`, `p`.`NullableInt`, `p`.`NullableInts`, `p`.`NullableString`, `p`.`NullableStrings`, `p`.`String`, `p`.`Strings` +FROM `PrimitiveCollectionsEntity` AS `p` +WHERE `p`.`String` IN ('10', '999') +""", + // + """ +SELECT `p`.`Id`, `p`.`Bool`, `p`.`Bools`, `p`.`DateTime`, `p`.`DateTimes`, `p`.`Enum`, `p`.`Enums`, `p`.`Int`, `p`.`Ints`, `p`.`NullableInt`, `p`.`NullableInts`, `p`.`NullableString`, `p`.`NullableStrings`, `p`.`String`, `p`.`Strings` +FROM `PrimitiveCollectionsEntity` AS `p` +WHERE `p`.`String` NOT IN ('10', '999') +"""); + } + + public override async Task Parameter_collection_of_nullable_strings_Contains_string(bool async) + { + await base.Parameter_collection_of_nullable_strings_Contains_string(async); + + AssertSql( +""" +SELECT `p`.`Id`, `p`.`Bool`, `p`.`Bools`, `p`.`DateTime`, `p`.`DateTimes`, `p`.`Enum`, `p`.`Enums`, `p`.`Int`, `p`.`Ints`, `p`.`NullableInt`, `p`.`NullableInts`, `p`.`NullableString`, `p`.`NullableStrings`, `p`.`String`, `p`.`Strings` +FROM `PrimitiveCollectionsEntity` AS `p` +WHERE `p`.`String` = '10' +""", + // + """ +SELECT `p`.`Id`, `p`.`Bool`, `p`.`Bools`, `p`.`DateTime`, `p`.`DateTimes`, `p`.`Enum`, `p`.`Enums`, `p`.`Int`, `p`.`Ints`, `p`.`NullableInt`, `p`.`NullableInts`, `p`.`NullableString`, `p`.`NullableStrings`, `p`.`String`, `p`.`Strings` +FROM `PrimitiveCollectionsEntity` AS `p` +WHERE `p`.`String` <> '10' +"""); + } + + public override async Task Parameter_collection_of_nullable_strings_Contains_nullable_string(bool async) + { + await base.Parameter_collection_of_nullable_strings_Contains_nullable_string(async); + + AssertSql( +""" +SELECT `p`.`Id`, `p`.`Bool`, `p`.`Bools`, `p`.`DateTime`, `p`.`DateTimes`, `p`.`Enum`, `p`.`Enums`, `p`.`Int`, `p`.`Ints`, `p`.`NullableInt`, `p`.`NullableInts`, `p`.`NullableString`, `p`.`NullableStrings`, `p`.`String`, `p`.`Strings` +FROM `PrimitiveCollectionsEntity` AS `p` +WHERE `p`.`NullableString` IS NULL OR (`p`.`NullableString` = '999') +""", + // + """ +SELECT `p`.`Id`, `p`.`Bool`, `p`.`Bools`, `p`.`DateTime`, `p`.`DateTimes`, `p`.`Enum`, `p`.`Enums`, `p`.`Int`, `p`.`Ints`, `p`.`NullableInt`, `p`.`NullableInts`, `p`.`NullableString`, `p`.`NullableStrings`, `p`.`String`, `p`.`Strings` +FROM `PrimitiveCollectionsEntity` AS `p` +WHERE `p`.`NullableString` IS NOT NULL AND (`p`.`NullableString` <> '999') +"""); + } + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/TPCGearsOfWarQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/TPCGearsOfWarQuerySingleStoreTest.cs index 04be62ec0..eb0e034f3 100644 --- a/test/EFCore.SingleStore.FunctionalTests/Query/TPCGearsOfWarQuerySingleStoreTest.cs +++ b/test/EFCore.SingleStore.FunctionalTests/Query/TPCGearsOfWarQuerySingleStoreTest.cs @@ -2913,9 +2913,9 @@ public override async Task Non_unicode_string_literals_is_used_for_non_unicode_c """); } - public override async Task Non_unicode_string_literals_is_used_for_non_unicode_column_with_concat(bool async) + public override async Task Unicode_string_literals_is_used_for_non_unicode_column_with_concat(bool async) { - await base.Non_unicode_string_literals_is_used_for_non_unicode_column_with_concat(async); + await base.Unicode_string_literals_is_used_for_non_unicode_column_with_concat(async); AssertSql( """ From 587212d7186e379b6017e8d0034eade7ed362367 Mon Sep 17 00:00:00 2001 From: Olha Kramarenko Date: Mon, 19 Jan 2026 16:56:43 +0200 Subject: [PATCH 4/8] grab changes for other test blocks --- ...EFCore.SingleStore.IntegrationTests.csproj | 12 +- .../EFCore.SingleStore.Tests.csproj | 16 +- .../Query/SingleStoreTimeZoneTest.cs | 137 ++++++++++++++++++ 3 files changed, 151 insertions(+), 14 deletions(-) create mode 100644 test/EFCore.SingleStore.Tests/Query/SingleStoreTimeZoneTest.cs diff --git a/test/EFCore.SingleStore.IntegrationTests/EFCore.SingleStore.IntegrationTests.csproj b/test/EFCore.SingleStore.IntegrationTests/EFCore.SingleStore.IntegrationTests.csproj index 23f822017..5220d34c9 100644 --- a/test/EFCore.SingleStore.IntegrationTests/EFCore.SingleStore.IntegrationTests.csproj +++ b/test/EFCore.SingleStore.IntegrationTests/EFCore.SingleStore.IntegrationTests.csproj @@ -47,22 +47,22 @@ - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Design.Tests\Debug\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Design.Tests\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.dll - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Design.Tests\Debug\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Abstractions.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Design.Tests\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Abstractions.dll - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Design.Tests\Debug\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Analyzers.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Design.Tests\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Analyzers.dll - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Design.Tests\Debug\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Design.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Design.Tests\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Design.dll - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Design.Tests\Debug\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Relational.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Design.Tests\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Relational.dll - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\Debug\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Specification.Tests.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Specification.Tests.dll diff --git a/test/EFCore.SingleStore.Tests/EFCore.SingleStore.Tests.csproj b/test/EFCore.SingleStore.Tests/EFCore.SingleStore.Tests.csproj index fae1599e4..f95116bcd 100644 --- a/test/EFCore.SingleStore.Tests/EFCore.SingleStore.Tests.csproj +++ b/test/EFCore.SingleStore.Tests/EFCore.SingleStore.Tests.csproj @@ -41,28 +41,28 @@ - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\Debug\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.dll - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\Debug\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Abstractions.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Abstractions.dll - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\Debug\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Analyzers.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Analyzers.dll - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Design.Tests\Debug\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Design.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Design.Tests\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Design.dll - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\Debug\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Proxies.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Proxies.dll - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\Debug\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Relational.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Relational.dll - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\Debug\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Relational.Specification.Tests.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Relational.Specification.Tests.dll - $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\Debug\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Specification.Tests.dll + $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\$(LocalEFCoreRepositoryConfiguration)\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Specification.Tests.dll diff --git a/test/EFCore.SingleStore.Tests/Query/SingleStoreTimeZoneTest.cs b/test/EFCore.SingleStore.Tests/Query/SingleStoreTimeZoneTest.cs new file mode 100644 index 000000000..eaf1f3dd2 --- /dev/null +++ b/test/EFCore.SingleStore.Tests/Query/SingleStoreTimeZoneTest.cs @@ -0,0 +1,137 @@ +using System; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using Xunit; + +namespace EntityFrameworkCore.SingleStore.Query +{ + public sealed class SingleStoreTimeZoneTest : TestWithFixture + { + public SingleStoreTimeZoneTest(SingleStoreTimeZoneFixture fixture) + : base(fixture) + { + } + + [ConditionalFact] + public void ConvertTimeZone() + { + using var context = Fixture.CreateContext(); + SetSessionTimeZone(context); + Fixture.ClearSql(); + + var metalContainer = context.Set() + .Where(c => c.DeliveredDateTimeOffset == c.DeliveredDateTimeUtc && + EF.Functions.ConvertTimeZone(c.DeliveredDateTimeOffset, c.DeliveredTimeZone) == c.DeliveredDateTimeLocal) + .Select( + c => new + { + c.DeliveredDateTimeUtc, + c.DeliveredDateTimeLocal, + c.DeliveredDateTimeOffset, + c.DeliveredTimeZone, + DeliveredWithAppliedTimeZone = EF.Functions.ConvertTimeZone(c.DeliveredDateTimeOffset, c.DeliveredTimeZone), + DeliveredConvertedToDifferent = EF.Functions.ConvertTimeZone(c.DeliveredDateTimeLocal, c.DeliveredTimeZone, "+06:00"), + }) + .Single(); + + Assert.Equal(SingleStoreTimeZoneFixture.OriginalDateTimeUtc, metalContainer.DeliveredDateTimeUtc); + Assert.Equal(SingleStoreTimeZoneFixture.OriginalDateTime, metalContainer.DeliveredDateTimeLocal); + Assert.Equal(SingleStoreTimeZoneFixture.OriginalDateTimeOffset, metalContainer.DeliveredDateTimeOffset); + Assert.Equal(SingleStoreTimeZoneFixture.OriginalDateTimeOffset.UtcDateTime, metalContainer.DeliveredDateTimeOffset.DateTime); + Assert.Equal(TimeSpan.Zero, metalContainer.DeliveredDateTimeOffset.Offset); + Assert.Equal(SingleStoreTimeZoneFixture.OriginalDateTime, metalContainer.DeliveredWithAppliedTimeZone); + Assert.Equal(SingleStoreTimeZoneFixture.OriginalDateTimeUtc.AddHours(6), metalContainer.DeliveredConvertedToDifferent); + + Assert.Equal( + """ +SELECT `c`.`DeliveredDateTimeUtc`, `c`.`DeliveredDateTimeLocal`, `c`.`DeliveredDateTimeOffset`, `c`.`DeliveredTimeZone`, CONVERT_TZ(`c`.`DeliveredDateTimeOffset`, '+00:00', `c`.`DeliveredTimeZone`) AS `DeliveredWithAppliedTimeZone`, CONVERT_TZ(`c`.`DeliveredDateTimeLocal`, `c`.`DeliveredTimeZone`, '+06:00') AS `DeliveredConvertedToDifferent` +FROM `Container` AS `c` +WHERE (`c`.`DeliveredDateTimeOffset` = `c`.`DeliveredDateTimeUtc`) AND (CONVERT_TZ(`c`.`DeliveredDateTimeOffset`, '+00:00', `c`.`DeliveredTimeZone`) = `c`.`DeliveredDateTimeLocal`) +LIMIT 2 +""", + Fixture.Sql); + } + + [ConditionalFact] + public void DateTimeOffset_LocalDateTime() + { + using var context = Fixture.CreateContext(); + SetSessionTimeZone(context); + Fixture.ClearSql(); + + var metalContainer = context.Set() + .Where(c => c.DeliveredDateTimeOffset.LocalDateTime == SingleStoreTimeZoneFixture.OriginalDateTimeUtc.AddHours(SingleStoreTimeZoneFixture.SessionOffset)) + .Select(c => new { c.DeliveredDateTimeOffset.LocalDateTime }) + .Single(); + + Assert.Equal(SingleStoreTimeZoneFixture.OriginalDateTimeUtc.AddHours(SingleStoreTimeZoneFixture.SessionOffset), metalContainer.LocalDateTime); + + Assert.Equal( + """ +SELECT CONVERT_TZ(`c`.`DeliveredDateTimeOffset`, '+00:00', @@session.time_zone) AS `LocalDateTime` +FROM `Container` AS `c` +WHERE CONVERT_TZ(`c`.`DeliveredDateTimeOffset`, '+00:00', @@session.time_zone) = TIMESTAMP '2023-12-31 15:00:00' +LIMIT 2 +""", + Fixture.Sql); + } + + private static void SetSessionTimeZone(SingleStoreTimeZoneFixture.SingleStoreTimeZoneContext context) + { + context.Database.OpenConnection(); + var connection = context.Database.GetDbConnection(); + using var command = connection.CreateCommand(); + command.CommandText = "SET @@session.time_zone = '-08:00';"; + command.ExecuteNonQuery(); + } + + public class SingleStoreTimeZoneFixture : SingleStoreTestFixtureBase + { + public const int SessionOffset = -8; // UTC-8 + public const int OriginalOffset = 2; // UTC+2 + public static readonly DateTime OriginalDateTimeUtc = new DateTime(2023, 12, 31, 23, 0, 0); + public static readonly DateTime OriginalDateTime = OriginalDateTimeUtc.AddHours(OriginalOffset); + public static readonly DateTimeOffset OriginalDateTimeOffset = new DateTimeOffset(OriginalDateTime, TimeSpan.FromHours(OriginalOffset)); + + public void ClearSql() + => base.SqlCommands.Clear(); + + public new string Sql + => base.Sql; + + public class SingleStoreTimeZoneContext : ContextBase + { + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity( + entity => + { + entity.HasData( + new Model.Container + { + Id = 1, + Name = "Heavymetal", + DeliveredDateTimeUtc = OriginalDateTimeUtc, + DeliveredDateTimeLocal = OriginalDateTime, + DeliveredDateTimeOffset = OriginalDateTimeOffset, + DeliveredTimeZone = "+02:00", + }); + }); + } + } + } + + private static class Model + { + public class Container + { + public int Id { get ; set; } + public string Name { get ; set; } + public DateTime DeliveredDateTimeUtc { get; set; } + public DateTime DeliveredDateTimeLocal { get; set; } + public DateTimeOffset DeliveredDateTimeOffset { get; set; } + public string DeliveredTimeZone { get; set; } + } + } + } +} From 5938e401996ca8b155d1bb4cdbd3b64c6d94676b Mon Sep 17 00:00:00 2001 From: Olha Kramarenko Date: Wed, 21 Jan 2026 13:12:07 +0200 Subject: [PATCH 5/8] try this fix for SingleStoreTimeZoneTest --- .github/workflows/test_setup/config.json | 2 +- .../Query/SingleStoreTimeZoneTest.cs | 6 +++--- test/EFCore.SingleStore.Tests/config.json.example | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test_setup/config.json b/.github/workflows/test_setup/config.json index e1fc6d4f2..0a0c095b8 100644 --- a/.github/workflows/test_setup/config.json +++ b/.github/workflows/test_setup/config.json @@ -1,6 +1,6 @@ { "Data": { - "ConnectionString": "server=SINGLESTORE_HOST;user id=SQL_USER_NAME;password=SQL_USER_PASSWORD;port=3306;database=singlestoretest;sslmode=None", + "ConnectionString": "server=SINGLESTORE_HOST;user id=SQL_USER_NAME;password=SQL_USER_PASSWORD;port=3306;database=singlestoretest;sslmode=None;allow user variables=True;", "ServerVersion": "auto", "CommandTimeout": "600" } diff --git a/test/EFCore.SingleStore.Tests/Query/SingleStoreTimeZoneTest.cs b/test/EFCore.SingleStore.Tests/Query/SingleStoreTimeZoneTest.cs index eaf1f3dd2..55f1df3a7 100644 --- a/test/EFCore.SingleStore.Tests/Query/SingleStoreTimeZoneTest.cs +++ b/test/EFCore.SingleStore.Tests/Query/SingleStoreTimeZoneTest.cs @@ -68,9 +68,9 @@ public void DateTimeOffset_LocalDateTime() Assert.Equal( """ -SELECT CONVERT_TZ(`c`.`DeliveredDateTimeOffset`, '+00:00', @@session.time_zone) AS `LocalDateTime` +SELECT CONVERT_TZ(`c`.`DeliveredDateTimeOffset`, '+00:00', @__ef_singlestore_tz) AS `LocalDateTime` FROM `Container` AS `c` -WHERE CONVERT_TZ(`c`.`DeliveredDateTimeOffset`, '+00:00', @@session.time_zone) = TIMESTAMP '2023-12-31 15:00:00' +WHERE CONVERT_TZ(`c`.`DeliveredDateTimeOffset`, '+00:00', @__ef_singlestore_tz) = ('2023-12-31 15:00:00' :> TIMESTAMP) LIMIT 2 """, Fixture.Sql); @@ -81,7 +81,7 @@ private static void SetSessionTimeZone(SingleStoreTimeZoneFixture.SingleStoreTim context.Database.OpenConnection(); var connection = context.Database.GetDbConnection(); using var command = connection.CreateCommand(); - command.CommandText = "SET @@session.time_zone = '-08:00';"; + command.CommandText = "SET @__ef_singlestore_tz = '-08:00';"; command.ExecuteNonQuery(); } diff --git a/test/EFCore.SingleStore.Tests/config.json.example b/test/EFCore.SingleStore.Tests/config.json.example index c944e6fa7..efb3cdfac 100644 --- a/test/EFCore.SingleStore.Tests/config.json.example +++ b/test/EFCore.SingleStore.Tests/config.json.example @@ -4,4 +4,4 @@ "ServerVersion": "auto", "CommandTimeout": "600" } -} \ No newline at end of file +} From bdf86efbdb1874206061a84b43206c414f9c6df3 Mon Sep 17 00:00:00 2001 From: Olha Kramarenko Date: Wed, 21 Jan 2026 14:25:20 +0200 Subject: [PATCH 6/8] fix time zone handling --- .../Internal/SingleStoreOptionsExtension.cs | 8 +++++ .../SingleStoreDbContextOptionsBuilder.cs | 14 +++++++++ ...reDbFunctionsExtensionsMethodTranslator.cs | 30 +++++++++++++++---- .../SingleStoreDateTimeMemberTranslator.cs | 15 ++++++++-- .../SingleStoreMemberTranslatorProvider.cs | 5 ++-- ...SingleStoreMethodCallTranslatorProvider.cs | 6 ++-- .../Query/SingleStoreTimeZoneTest.cs | 21 ++++--------- 7 files changed, 72 insertions(+), 27 deletions(-) diff --git a/src/EFCore.SingleStore/Infrastructure/Internal/SingleStoreOptionsExtension.cs b/src/EFCore.SingleStore/Infrastructure/Internal/SingleStoreOptionsExtension.cs index 90827543a..f9c77fa86 100644 --- a/src/EFCore.SingleStore/Infrastructure/Internal/SingleStoreOptionsExtension.cs +++ b/src/EFCore.SingleStore/Infrastructure/Internal/SingleStoreOptionsExtension.cs @@ -92,6 +92,7 @@ protected override RelationalOptionsExtension Clone() public virtual bool LimitKeyedOrIndexedStringColumnLength { get; private set; } public virtual bool StringComparisonTranslations { get; private set; } public virtual bool PrimitiveCollectionsSupport { get; private set; } + public string SessionTimeZone { get; private set; } /// /// Creates a new instance with all options the same as for this instance, but with the given option changed. @@ -216,6 +217,13 @@ public virtual SingleStoreOptionsExtension WithPrimitiveCollectionsSupport(bool return clone; } + public SingleStoreOptionsExtension WithSessionTimeZone(string offset) + { + var clone = (SingleStoreOptionsExtension)Clone(); + clone.SessionTimeZone = offset; + return clone; + } + /// /// 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 diff --git a/src/EFCore.SingleStore/Infrastructure/SingleStoreDbContextOptionsBuilder.cs b/src/EFCore.SingleStore/Infrastructure/SingleStoreDbContextOptionsBuilder.cs index f6d0caa57..a0f9c3fac 100644 --- a/src/EFCore.SingleStore/Infrastructure/SingleStoreDbContextOptionsBuilder.cs +++ b/src/EFCore.SingleStore/Infrastructure/SingleStoreDbContextOptionsBuilder.cs @@ -131,5 +131,19 @@ public virtual SingleStoreDbContextOptionsBuilder EnableStringComparisonTranslat /// public virtual SingleStoreDbContextOptionsBuilder EnablePrimitiveCollectionsSupport(bool enable = true) => WithOption(e => e.WithPrimitiveCollectionsSupport(enable)); + + public virtual SingleStoreDbContextOptionsBuilder SessionTimeZone(string offset) + { + if (string.IsNullOrWhiteSpace(offset)) + throw new ArgumentException("Session time zone offset must be a value like '+02:00' or '-08:00'.", nameof(offset)); + + var extension = OptionsBuilder.Options.FindExtension() + ?? new SingleStoreOptionsExtension(); + + ((IDbContextOptionsBuilderInfrastructure)OptionsBuilder) + .AddOrUpdateExtension(extension.WithSessionTimeZone(offset)); + + return this; + } } } diff --git a/src/EFCore.SingleStore/Query/ExpressionTranslators/Internal/SingleStoreDbFunctionsExtensionsMethodTranslator.cs b/src/EFCore.SingleStore/Query/ExpressionTranslators/Internal/SingleStoreDbFunctionsExtensionsMethodTranslator.cs index d614075f4..f8d1842d7 100644 --- a/src/EFCore.SingleStore/Query/ExpressionTranslators/Internal/SingleStoreDbFunctionsExtensionsMethodTranslator.cs +++ b/src/EFCore.SingleStore/Query/ExpressionTranslators/Internal/SingleStoreDbFunctionsExtensionsMethodTranslator.cs @@ -6,8 +6,10 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using EntityFrameworkCore.SingleStore.Infrastructure.Internal; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; using Microsoft.EntityFrameworkCore.Storage; @@ -23,8 +25,9 @@ namespace EntityFrameworkCore.SingleStore.Query.ExpressionTranslators.Internal public class SingleStoreDbFunctionsExtensionsMethodTranslator : IMethodCallTranslator { private readonly SingleStoreSqlExpressionFactory _sqlExpressionFactory; + private readonly string _sessionTimeZone; - private static readonly HashSet _convertTimeZoneMethodInfos = + private static readonly HashSet _convertTimeZoneMethodInfos = [ typeof(SingleStoreDbFunctionsExtensions).GetRuntimeMethod( nameof(SingleStoreDbFunctionsExtensions.ConvertTimeZone), @@ -169,9 +172,13 @@ private static readonly MethodInfo[] _hexMethodInfos private static readonly MethodInfo _radiansDoubleMethodInfo = typeof(SingleStoreDbFunctionsExtensions).GetRuntimeMethod(nameof(SingleStoreDbFunctionsExtensions.Radians), new[] { typeof(DbFunctions), typeof(double) }); private static readonly MethodInfo _radiansFloatMethodInfo = typeof(SingleStoreDbFunctionsExtensions).GetRuntimeMethod(nameof(SingleStoreDbFunctionsExtensions.Radians), new[] { typeof(DbFunctions), typeof(float) }); - public SingleStoreDbFunctionsExtensionsMethodTranslator(ISqlExpressionFactory sqlExpressionFactory) + public SingleStoreDbFunctionsExtensionsMethodTranslator(ISqlExpressionFactory sqlExpressionFactory, IDbContextOptions dbContextOptions) { _sqlExpressionFactory = (SingleStoreSqlExpressionFactory)sqlExpressionFactory; + // Default to UTC when not configured, because SingleStore ignores @@session.time_zone at runtime. + _sessionTimeZone = + dbContextOptions.FindExtension()?.SessionTimeZone + ?? "+00:00"; } /// @@ -186,6 +193,12 @@ public virtual SqlExpression Translate( { if (_convertTimeZoneMethodInfos.TryGetValue(method, out _)) { + // Replace any accidental @@session.time_zone fragments (MySQL semantics) with configured session offset. + SqlExpression RewriteSessionTz(SqlExpression expr) + => expr is SqlFragmentExpression sfe && sfe.Sql == "@@session.time_zone" + ? _sqlExpressionFactory.Constant(_sessionTimeZone) + : expr; + // Will not just return `NULL` if any of its parameters is `NULL`, but also if `fromTimeZone` or `toTimeZone` is incorrect. // Will do no conversion at all if `dateTime` is outside the supported range. return _sqlExpressionFactory.NullableFunction( @@ -193,14 +206,19 @@ public virtual SqlExpression Translate( arguments.Count == 3 ? [ - arguments[1], + RewriteSessionTz(arguments[1]), // The implicit fromTimeZone is UTC for DateTimeOffset values and the current session time zone otherwise. method.GetParameters()[1].ParameterType.UnwrapNullableType() == typeof(DateTimeOffset) ? _sqlExpressionFactory.Constant("+00:00") - : _sqlExpressionFactory.Fragment("@@session.time_zone"), - arguments[2] + : _sqlExpressionFactory.Constant(_sessionTimeZone), // was @@session.time_zone + RewriteSessionTz(arguments[2]) ] - : new[] { arguments[1], arguments[2], arguments[3] }, + : new[] + { + RewriteSessionTz(arguments[1]), + RewriteSessionTz(arguments[2]), + RewriteSessionTz(arguments[3]) + }, method.ReturnType.UnwrapNullableType(), null, false, diff --git a/src/EFCore.SingleStore/Query/Internal/SingleStoreDateTimeMemberTranslator.cs b/src/EFCore.SingleStore/Query/Internal/SingleStoreDateTimeMemberTranslator.cs index 1bdb2cbf5..463925177 100644 --- a/src/EFCore.SingleStore/Query/Internal/SingleStoreDateTimeMemberTranslator.cs +++ b/src/EFCore.SingleStore/Query/Internal/SingleStoreDateTimeMemberTranslator.cs @@ -5,8 +5,10 @@ using System; using System.Collections.Generic; using System.Reflection; +using EntityFrameworkCore.SingleStore.Infrastructure.Internal; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; using EntityFrameworkCore.SingleStore.Utilities; @@ -27,10 +29,15 @@ public class SingleStoreDateTimeMemberTranslator : IMemberTranslator { nameof(DateTime.Millisecond), ("microsecond", 1000) }, }; private readonly SingleStoreSqlExpressionFactory _sqlExpressionFactory; + private readonly string _sessionTimeZone; - public SingleStoreDateTimeMemberTranslator(ISqlExpressionFactory sqlExpressionFactory) + public SingleStoreDateTimeMemberTranslator(ISqlExpressionFactory sqlExpressionFactory, IDbContextOptions dbContextOptions) { _sqlExpressionFactory = (SingleStoreSqlExpressionFactory)sqlExpressionFactory; + + // Read the configured session time zone offset (e.g. "-08:00") from provider options. + // If not configured, we default to "+00:00" (UTC) because SingleStore ignores @@session.time_zone at runtime. + _sessionTimeZone = dbContextOptions.FindExtension()?.SessionTimeZone ?? "+00:00"; } public virtual SqlExpression Translate( @@ -156,7 +163,11 @@ public virtual SqlExpression Translate( case nameof(DateTimeOffset.LocalDateTime): return _sqlExpressionFactory.NullableFunction( "CONVERT_TZ", - [instance, _sqlExpressionFactory.Constant("+00:00"), _sqlExpressionFactory.Fragment("@@session.time_zone")], + [ + instance, + _sqlExpressionFactory.Constant("+00:00"), + _sqlExpressionFactory.Constant(_sessionTimeZone) // use provider-configured offset (SingleStore ignores @@session.time_zone) + ], typeof(DateTime), null, false, diff --git a/src/EFCore.SingleStore/Query/Internal/SingleStoreMemberTranslatorProvider.cs b/src/EFCore.SingleStore/Query/Internal/SingleStoreMemberTranslatorProvider.cs index 7e3ba27f9..67c2080b9 100644 --- a/src/EFCore.SingleStore/Query/Internal/SingleStoreMemberTranslatorProvider.cs +++ b/src/EFCore.SingleStore/Query/Internal/SingleStoreMemberTranslatorProvider.cs @@ -3,20 +3,21 @@ // Licensed under the MIT. See LICENSE in the project root for license information. using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Query; namespace EntityFrameworkCore.SingleStore.Query.Internal { public class SingleStoreMemberTranslatorProvider : RelationalMemberTranslatorProvider { - public SingleStoreMemberTranslatorProvider([NotNull] RelationalMemberTranslatorProviderDependencies dependencies) + public SingleStoreMemberTranslatorProvider([NotNull] RelationalMemberTranslatorProviderDependencies dependencies, IDbContextOptions dbContextOptions) : base(dependencies) { var sqlExpressionFactory = (SingleStoreSqlExpressionFactory)dependencies.SqlExpressionFactory; AddTranslators( new IMemberTranslator[] { - new SingleStoreDateTimeMemberTranslator(sqlExpressionFactory), + new SingleStoreDateTimeMemberTranslator(sqlExpressionFactory, dbContextOptions), new SingleStoreStringMemberTranslator(sqlExpressionFactory), new SingleStoreTimeSpanMemberTranslator(sqlExpressionFactory), }); diff --git a/src/EFCore.SingleStore/Query/Internal/SingleStoreMethodCallTranslatorProvider.cs b/src/EFCore.SingleStore/Query/Internal/SingleStoreMethodCallTranslatorProvider.cs index 477800d3b..2fe97ad77 100644 --- a/src/EFCore.SingleStore/Query/Internal/SingleStoreMethodCallTranslatorProvider.cs +++ b/src/EFCore.SingleStore/Query/Internal/SingleStoreMethodCallTranslatorProvider.cs @@ -14,6 +14,7 @@ using EntityFrameworkCore.SingleStore.Infrastructure.Internal; using EntityFrameworkCore.SingleStore.Query.ExpressionTranslators.Internal; using EntityFrameworkCore.SingleStore.Storage.Internal; +using Microsoft.EntityFrameworkCore.Infrastructure; namespace EntityFrameworkCore.SingleStore.Query.Internal { @@ -21,7 +22,8 @@ public class SingleStoreMethodCallTranslatorProvider : RelationalMethodCallTrans { public SingleStoreMethodCallTranslatorProvider( [NotNull] RelationalMethodCallTranslatorProviderDependencies dependencies, - [NotNull] ISingleStoreOptions options) + [NotNull] ISingleStoreOptions options, + [NotNull] IDbContextOptions dbContextOptions) : base(dependencies) { var sqlExpressionFactory = (SingleStoreSqlExpressionFactory)dependencies.SqlExpressionFactory; @@ -33,7 +35,7 @@ public SingleStoreMethodCallTranslatorProvider( new SingleStoreConvertTranslator(sqlExpressionFactory), new SingleStoreDateTimeMethodTranslator(sqlExpressionFactory), new SingleStoreDateDiffFunctionsTranslator(sqlExpressionFactory), - new SingleStoreDbFunctionsExtensionsMethodTranslator(sqlExpressionFactory), + new SingleStoreDbFunctionsExtensionsMethodTranslator(sqlExpressionFactory, dbContextOptions), new SingleStoreJsonDbFunctionsTranslator(sqlExpressionFactory), new SingleStoreMathMethodTranslator(sqlExpressionFactory), new SingleStoreNewGuidTranslator(sqlExpressionFactory), diff --git a/test/EFCore.SingleStore.Tests/Query/SingleStoreTimeZoneTest.cs b/test/EFCore.SingleStore.Tests/Query/SingleStoreTimeZoneTest.cs index 55f1df3a7..233eab9c6 100644 --- a/test/EFCore.SingleStore.Tests/Query/SingleStoreTimeZoneTest.cs +++ b/test/EFCore.SingleStore.Tests/Query/SingleStoreTimeZoneTest.cs @@ -15,8 +15,8 @@ public SingleStoreTimeZoneTest(SingleStoreTimeZoneFixture fixture) [ConditionalFact] public void ConvertTimeZone() { - using var context = Fixture.CreateContext(); - SetSessionTimeZone(context); + using var context = Fixture.CreateContext( + mySqlOptions: o => o.SessionTimeZone("-08:00")); // use provider option (customer-facing) Fixture.ClearSql(); var metalContainer = context.Set() @@ -55,8 +55,8 @@ LIMIT 2 [ConditionalFact] public void DateTimeOffset_LocalDateTime() { - using var context = Fixture.CreateContext(); - SetSessionTimeZone(context); + using var context = Fixture.CreateContext( + mySqlOptions: o => o.SessionTimeZone("-08:00")); // use provider option (customer-facing) Fixture.ClearSql(); var metalContainer = context.Set() @@ -68,23 +68,14 @@ public void DateTimeOffset_LocalDateTime() Assert.Equal( """ -SELECT CONVERT_TZ(`c`.`DeliveredDateTimeOffset`, '+00:00', @__ef_singlestore_tz) AS `LocalDateTime` +SELECT CONVERT_TZ(`c`.`DeliveredDateTimeOffset`, '+00:00', '-08:00') AS `LocalDateTime` FROM `Container` AS `c` -WHERE CONVERT_TZ(`c`.`DeliveredDateTimeOffset`, '+00:00', @__ef_singlestore_tz) = ('2023-12-31 15:00:00' :> TIMESTAMP) +WHERE CONVERT_TZ(`c`.`DeliveredDateTimeOffset`, '+00:00', '-08:00') = '2023-12-31 15:00:00' LIMIT 2 """, Fixture.Sql); } - private static void SetSessionTimeZone(SingleStoreTimeZoneFixture.SingleStoreTimeZoneContext context) - { - context.Database.OpenConnection(); - var connection = context.Database.GetDbConnection(); - using var command = connection.CreateCommand(); - command.CommandText = "SET @__ef_singlestore_tz = '-08:00';"; - command.ExecuteNonQuery(); - } - public class SingleStoreTimeZoneFixture : SingleStoreTestFixtureBase { public const int SessionOffset = -8; // UTC-8 From 35fb6c56a7dfd289c993d598b64cb51dc940718e Mon Sep 17 00:00:00 2001 From: Olha Kramarenko Date: Wed, 21 Jan 2026 16:44:13 +0200 Subject: [PATCH 7/8] fix failing FunctionalTests --- .../Internal/SingleStoreOptionsExtension.cs | 4 +-- .../ConnectionSingleStoreTest.cs | 2 +- ...MigrationsInfrastructureSingleStoreTest.cs | 26 +++++++++++++++---- .../Query/FunkyDataQuerySingleStoreTest.cs | 1 + ...thwindMiscellaneousQuerySingleStoreTest.cs | 1 + 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/EFCore.SingleStore/Infrastructure/Internal/SingleStoreOptionsExtension.cs b/src/EFCore.SingleStore/Infrastructure/Internal/SingleStoreOptionsExtension.cs index f9c77fa86..8f8cd233a 100644 --- a/src/EFCore.SingleStore/Infrastructure/Internal/SingleStoreOptionsExtension.cs +++ b/src/EFCore.SingleStore/Infrastructure/Internal/SingleStoreOptionsExtension.cs @@ -92,7 +92,7 @@ protected override RelationalOptionsExtension Clone() public virtual bool LimitKeyedOrIndexedStringColumnLength { get; private set; } public virtual bool StringComparisonTranslations { get; private set; } public virtual bool PrimitiveCollectionsSupport { get; private set; } - public string SessionTimeZone { get; private set; } + public virtual string SessionTimeZone { get; private set; } /// /// Creates a new instance with all options the same as for this instance, but with the given option changed. @@ -217,7 +217,7 @@ public virtual SingleStoreOptionsExtension WithPrimitiveCollectionsSupport(bool return clone; } - public SingleStoreOptionsExtension WithSessionTimeZone(string offset) + public virtual SingleStoreOptionsExtension WithSessionTimeZone(string offset) { var clone = (SingleStoreOptionsExtension)Clone(); clone.SessionTimeZone = offset; diff --git a/test/EFCore.SingleStore.FunctionalTests/ConnectionSingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/ConnectionSingleStoreTest.cs index 82085471b..b8fb5c9d9 100644 --- a/test/EFCore.SingleStore.FunctionalTests/ConnectionSingleStoreTest.cs +++ b/test/EFCore.SingleStore.FunctionalTests/ConnectionSingleStoreTest.cs @@ -171,7 +171,7 @@ public void Can_create_admin_connection_with_connection() } - [Fact] + [ConditionalFact(Skip = "Feature 'Non-underscore or alphanumeric characters in database name, or name starts with digit' is not supported by SingleStore")] public void Can_create_database_with_disablebackslashescaping() { var optionsBuilder = new DbContextOptionsBuilder(); diff --git a/test/EFCore.SingleStore.FunctionalTests/MigrationsInfrastructureSingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/MigrationsInfrastructureSingleStoreTest.cs index c0a05dba7..6f2334429 100644 --- a/test/EFCore.SingleStore.FunctionalTests/MigrationsInfrastructureSingleStoreTest.cs +++ b/test/EFCore.SingleStore.FunctionalTests/MigrationsInfrastructureSingleStoreTest.cs @@ -91,6 +91,14 @@ public override void Can_generate_up_scripts() COMMIT; +START TRANSACTION; + +INSERT INTO `__EFMigrationsHistory` (`MigrationId`, `ProductVersion`) +VALUES ('00000000000004_Migration4', '7.0.0-test'); + +COMMIT; + + """, Sql, ignoreLineEndingDifferences: true); @@ -110,11 +118,7 @@ public override void Can_generate_one_up_script() COMMIT; -START TRANSACTION; -INSERT INTO `__EFMigrationsHistory` (`MigrationId`, `ProductVersion`) -VALUES ('00000000000004_Migration4', '7.0.0-test'); -COMMIT; -""", +", Sql, ignoreLineEndingDifferences: true); } @@ -240,19 +244,25 @@ IF NOT EXISTS(SELECT 1 FROM `__EFMigrationsHistory` WHERE `MigrationId` = '00000 COMMIT; START TRANSACTION; + DROP PROCEDURE IF EXISTS MigrationsScript; DELIMITER // CREATE PROCEDURE MigrationsScript() BEGIN IF NOT EXISTS(SELECT 1 FROM `__EFMigrationsHistory` WHERE `MigrationId` = '00000000000004_Migration4') THEN + INSERT INTO `__EFMigrationsHistory` (`MigrationId`, `ProductVersion`) VALUES ('00000000000004_Migration4', '7.0.0-test'); + END IF; END // DELIMITER ; CALL MigrationsScript(); DROP PROCEDURE MigrationsScript; + COMMIT; + + """, Sql, ignoreLineEndingDifferences: true); @@ -438,6 +448,8 @@ public override void Can_generate_up_scripts_noTransactions() INSERT INTO `__EFMigrationsHistory` (`MigrationId`, `ProductVersion`) VALUES ('00000000000004_Migration4', '7.0.0-test'); + + """, Sql, ignoreLineEndingDifferences: true); @@ -537,13 +549,17 @@ IF NOT EXISTS(SELECT 1 FROM `__EFMigrationsHistory` WHERE `MigrationId` = '00000 CREATE PROCEDURE MigrationsScript() BEGIN IF NOT EXISTS(SELECT 1 FROM `__EFMigrationsHistory` WHERE `MigrationId` = '00000000000004_Migration4') THEN + INSERT INTO `__EFMigrationsHistory` (`MigrationId`, `ProductVersion`) VALUES ('00000000000004_Migration4', '7.0.0-test'); + END IF; END // DELIMITER ; CALL MigrationsScript(); DROP PROCEDURE MigrationsScript; + + """, Sql, ignoreLineEndingDifferences: true); diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/FunkyDataQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/FunkyDataQuerySingleStoreTest.cs index 37baec85c..bd0089ba9 100644 --- a/test/EFCore.SingleStore.FunctionalTests/Query/FunkyDataQuerySingleStoreTest.cs +++ b/test/EFCore.SingleStore.FunctionalTests/Query/FunkyDataQuerySingleStoreTest.cs @@ -183,6 +183,7 @@ public override async Task String_Contains_and_StartsWith_with_same_parameter(bo """ @__s_0_contains='%B%' (Size = 4000) @__s_0_startswith='B%' (Size = 4000) + SELECT `f`.`Id`, `f`.`FirstName`, `f`.`LastName`, `f`.`NullableBool` FROM `FunkyCustomers` AS `f` WHERE (`f`.`FirstName` LIKE @__s_0_contains) OR (`f`.`LastName` LIKE @__s_0_startswith) diff --git a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindMiscellaneousQuerySingleStoreTest.cs b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindMiscellaneousQuerySingleStoreTest.cs index 579c15ae4..d8072a924 100644 --- a/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindMiscellaneousQuerySingleStoreTest.cs +++ b/test/EFCore.SingleStore.FunctionalTests/Query/NorthwindMiscellaneousQuerySingleStoreTest.cs @@ -730,6 +730,7 @@ public override Task DefaultIfEmpty_Sum_over_collection_navigation(bool async) return base.DefaultIfEmpty_Sum_over_collection_navigation(async); } + [ConditionalTheory(Skip = "SingleStore does not support this type of query: correlated subselect in ORDER BY")] public override async Task Parameter_collection_Contains_with_projection_and_ordering(bool async) { #if EFCORE_DEBUG_BUILD From 43af9cea25ec5a322957831ea68a858892cd6732 Mon Sep 17 00:00:00 2001 From: Olha Kramarenko Date: Thu, 22 Jan 2026 13:32:28 +0200 Subject: [PATCH 8/8] update README with time zone handling info --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 1a534a7fb..42b33bc19 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,15 @@ Use the [EF Core tools](https://docs.microsoft.com/en-us/ef/core/cli/dotnet) to dotnet ef dbcontext scaffold "Server=localhost;User=root;Password=1234;Database=ef" "SingleStore.EntityFrameworkCore" ``` +## Time zone handling +SingleStore does not support changing [`@@session.time_zone`](https://docs.singlestore.com/db/v9.0/user-and-cluster-administration/maintain-your-cluster/setting-the-time-zone/time-zone-engine-variables/) at runtime (it is a MySQL-compatibility variable and remains `SYSTEM`). + +If you need `DateTimeOffset.LocalDateTime` translation, configure a session time zone offset in the provider: +```c# +optionsBuilder.UseSingleStore(cs, o => o.SessionTimeZone("-08:00")); +``` +If not configured, the provider defaults to `+00:00` (UTC). + ## License [MIT](https://github.com/memsql/SingleStore.EntityFrameworkCore/blob/master/LICENSE)