diff --git a/.editorconfig b/.editorconfig
index a241d5f86..7915b52ff 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -232,14 +232,6 @@ dotnet_naming_rule.const_field_naming.symbols = const_field_symbol
dotnet_naming_rule.const_field_naming.style = pascal_case_style
dotnet_naming_rule.const_field_naming.severity = suggestion
-# Private Fields
-dotnet_naming_symbols.private_field_symbol.applicable_kinds = field
-dotnet_naming_symbols.private_field_symbol.applicable_accessibilities = private
-
-dotnet_naming_rule.private_field_naming.symbols = private_field_symbol
-dotnet_naming_rule.private_field_naming.style = _camelCase
-dotnet_naming_rule.private_field_naming.severity = suggestion
-
# Parameters
dotnet_naming_symbols.parameter_symbol.applicable_kinds = parameter
dotnet_naming_symbols.parameter_symbol.applicable_accessibilities = *
diff --git a/Dependencies.targets b/Dependencies.targets
index 3191b6805..6a796c88f 100644
--- a/Dependencies.targets
+++ b/Dependencies.targets
@@ -1,9 +1,7 @@
- 6.0.2
- [$(DotnetRuntimeVersion), 7.0.0)
- $(DotnetRuntimeVersion)
- $(DotnetRuntimeVersion)
+ 7.*
+ 7.0.0
@@ -12,41 +10,40 @@
-
+
-
-
+
+
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
index ff50c725d..0b71e81a6 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -22,7 +22,10 @@
- net6.0
+ net6.0;net7.0
+ net7.0
+ net6.0
+ net7.0
diff --git a/Version.props b/Version.props
index e03581613..b1397fd7c 100644
--- a/Version.props
+++ b/Version.props
@@ -4,7 +4,7 @@
Use the following values for the different release types:
- "alpha" - EF Core release independent, code quality unstable, major changes
- "beta" - EF Core release independent, code quality stable, can introduce breaking changes
- - "silver" - EF Core release independent, code quality stable, can introduce breaking changes
+ - "silver" - EF Core release independent, code quality stable, only minor changes are expected
- "preview" - EF Core release targeted, code quality stable, can introduce breaking changes
- "rc" - EF Core release targeted, code quality production ready, only minor changes are expected
@@ -12,8 +12,8 @@
- "rtm" - EF Core release independent, code quality production ready, major release
- "servicing" - EF Core release independent, code quality production ready, mainly bugfixes
-->
- 6.0.2
- servicing
+ 7.0.0
+ alpha
1
- $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(TargetFramework)\Microsoft.EntityFrameworkCore.dll
+ $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.dll
- $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(TargetFramework)\Microsoft.EntityFrameworkCore.Abstractions.dll
+ $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Abstractions.dll
- $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(TargetFramework)\Microsoft.EntityFrameworkCore.Analyzers.dll
+ $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Analyzers.dll
- $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(TargetFramework)\Microsoft.EntityFrameworkCore.Relational.dll
+ $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Relational.dll
diff --git a/src/EFCore.MySql.Json.Newtonsoft/EFCore.MySql.Json.Newtonsoft.csproj b/src/EFCore.MySql.Json.Newtonsoft/EFCore.MySql.Json.Newtonsoft.csproj
index 9b91c6fc6..8b733b84d 100644
--- a/src/EFCore.MySql.Json.Newtonsoft/EFCore.MySql.Json.Newtonsoft.csproj
+++ b/src/EFCore.MySql.Json.Newtonsoft/EFCore.MySql.Json.Newtonsoft.csproj
@@ -2,7 +2,7 @@
JSON support using Newtonsoft.Json (JSON.NET) for Pomelo's MySQL provider for Entity Framework Core.
- $(DefaultNetCoreTargetFramework)
+ $(PomeloTargetFrameworks)
3.6
Pomelo.EntityFrameworkCore.MySql.Json.Newtonsoft
Pomelo.EntityFrameworkCore.MySql.Json.Newtonsoft
@@ -57,16 +57,16 @@
- $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(TargetFramework)\Microsoft.EntityFrameworkCore.dll
+ $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.dll
- $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(TargetFramework)\Microsoft.EntityFrameworkCore.Abstractions.dll
+ $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Abstractions.dll
- $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(TargetFramework)\Microsoft.EntityFrameworkCore.Analyzers.dll
+ $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Analyzers.dll
- $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(TargetFramework)\Microsoft.EntityFrameworkCore.Relational.dll
+ $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Relational.dll
diff --git a/src/EFCore.MySql.NTS/EFCore.MySql.NTS.csproj b/src/EFCore.MySql.NTS/EFCore.MySql.NTS.csproj
index b772c5c0b..e9d596e1e 100644
--- a/src/EFCore.MySql.NTS/EFCore.MySql.NTS.csproj
+++ b/src/EFCore.MySql.NTS/EFCore.MySql.NTS.csproj
@@ -2,7 +2,7 @@
NetTopologySuite support for Pomelo's MySQL provider for Entity Framework Core.
- $(DefaultNetCoreTargetFramework)
+ $(PomeloTargetFrameworks)
3.6
Pomelo.EntityFrameworkCore.MySql.NetTopologySuite
Pomelo.EntityFrameworkCore.MySql
@@ -57,16 +57,16 @@
- $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(TargetFramework)\Microsoft.EntityFrameworkCore.dll
+ $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.dll
- $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(TargetFramework)\Microsoft.EntityFrameworkCore.Abstractions.dll
+ $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Abstractions.dll
- $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(TargetFramework)\Microsoft.EntityFrameworkCore.Analyzers.dll
+ $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Analyzers.dll
- $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(TargetFramework)\Microsoft.EntityFrameworkCore.Relational.dll
+ $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Relational.dll
diff --git a/src/EFCore.MySql/EFCore.MySql.csproj b/src/EFCore.MySql/EFCore.MySql.csproj
index 2624ed51b..f72e20e6c 100644
--- a/src/EFCore.MySql/EFCore.MySql.csproj
+++ b/src/EFCore.MySql/EFCore.MySql.csproj
@@ -2,7 +2,7 @@
Pomelo's MySQL database provider for Entity Framework Core.
- $(DefaultNetCoreTargetFramework)
+ $(PomeloTargetFrameworks)
3.6
Pomelo.EntityFrameworkCore.MySql
Pomelo.EntityFrameworkCore.MySql
@@ -30,16 +30,16 @@
- $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(TargetFramework)\Microsoft.EntityFrameworkCore.dll
+ $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.dll
- $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(TargetFramework)\Microsoft.EntityFrameworkCore.Abstractions.dll
+ $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Abstractions.dll
- $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(TargetFramework)\Microsoft.EntityFrameworkCore.Analyzers.dll
+ $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Analyzers.dll
- $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(TargetFramework)\Microsoft.EntityFrameworkCore.Relational.dll
+ $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational\Debug\$(EfCoreTargetFramework)\Microsoft.EntityFrameworkCore.Relational.dll
@@ -60,7 +60,6 @@
-
diff --git a/src/EFCore.MySql/Extensions/MySqlDbContextOptionsBuilderExtensions.cs b/src/EFCore.MySql/Extensions/MySqlDbContextOptionsBuilderExtensions.cs
index e187baf1b..582345df0 100644
--- a/src/EFCore.MySql/Extensions/MySqlDbContextOptionsBuilderExtensions.cs
+++ b/src/EFCore.MySql/Extensions/MySqlDbContextOptionsBuilderExtensions.cs
@@ -91,27 +91,27 @@ public static DbContextOptionsBuilder UseMySql(
/// The options builder so that further configuration can be chained.
public static DbContextOptionsBuilder UseMySql(
[NotNull] this DbContextOptionsBuilder optionsBuilder,
- [NotNull] string connectionString,
+ [CanBeNull] string connectionString,
[NotNull] ServerVersion serverVersion,
[CanBeNull] Action mySqlOptionsAction = null)
{
Check.NotNull(optionsBuilder, nameof(optionsBuilder));
- Check.NotEmpty(connectionString, nameof(connectionString));
-
- var resolvedConnectionString = new NamedConnectionStringResolver(optionsBuilder.Options)
- .ResolveConnectionString(connectionString);
+ Check.NullButNotEmpty(connectionString, nameof(connectionString));
- var csb = new MySqlConnectionStringBuilder(resolvedConnectionString)
+ if (connectionString is not null)
{
- AllowUserVariables = true,
- UseAffectedRows = false
- };
+ var resolvedConnectionString = new NamedConnectionStringResolver(optionsBuilder.Options)
+ .ResolveConnectionString(connectionString);
- resolvedConnectionString = csb.ConnectionString;
+ // TODO: Move to MySqlRelationalConnection.
+ var csb = new MySqlConnectionStringBuilder(resolvedConnectionString) { AllowUserVariables = true, UseAffectedRows = false };
+
+ connectionString = csb.ConnectionString;
+ }
var extension = (MySqlOptionsExtension)GetOrCreateExtension(optionsBuilder)
.WithServerVersion(serverVersion)
- .WithConnectionString(resolvedConnectionString);
+ .WithConnectionString(connectionString);
((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);
ConfigureWarnings(optionsBuilder);
@@ -160,6 +160,7 @@ public static DbContextOptionsBuilder UseMySql(
.ResolveConnectionString(connection.ConnectionString)
: null;
+ // TODO: Move to MySqlRelationalConnection.
var csb = new MySqlConnectionStringBuilder(resolvedConnectionString);
if (!csb.AllowUserVariables ||
diff --git a/src/EFCore.MySql/Extensions/MySqlModelExtensions.cs b/src/EFCore.MySql/Extensions/MySqlModelExtensions.cs
index d31fff6ab..cffb119dc 100644
--- a/src/EFCore.MySql/Extensions/MySqlModelExtensions.cs
+++ b/src/EFCore.MySql/Extensions/MySqlModelExtensions.cs
@@ -19,8 +19,18 @@ public static class MySqlModelExtensions
///
/// The model.
/// The default .
- public static MySqlValueGenerationStrategy? GetValueGenerationStrategy([NotNull] this IModel model)
- => (MySqlValueGenerationStrategy?)model[MySqlAnnotationNames.ValueGenerationStrategy];
+ public static MySqlValueGenerationStrategy? GetValueGenerationStrategy([NotNull] this IReadOnlyModel model)
+ {
+ // Allow users to use the underlying type value instead of the enum itself.
+ // Workaround for: https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/issues/1205
+ if (model[MySqlAnnotationNames.ValueGenerationStrategy] is { } annotation &&
+ ObjectToEnumConverter.GetEnumValue(annotation) is { } enumValue)
+ {
+ return enumValue;
+ }
+
+ return null;
+ }
///
/// Attempts to set the to use for properties
@@ -63,7 +73,7 @@ public static void SetValueGenerationStrategy([NotNull] this IMutableModel model
///
/// The model.
/// The default character set.
- public static string GetCharSet([NotNull] this IModel model)
+ public static string GetCharSet([NotNull] this IReadOnlyModel model)
=> model[MySqlAnnotationNames.CharSet] as string;
///
@@ -104,7 +114,7 @@ public static string SetCharSet([NotNull] this IConventionModel model, string ch
///
/// The model.
/// The character set delegation modes.
- public static DelegationModes? GetCharSetDelegation([NotNull] this IModel model)
+ public static DelegationModes? GetCharSetDelegation([NotNull] this IReadOnlyModel model)
=> ObjectToEnumConverter.GetEnumValue(model[MySqlAnnotationNames.CharSetDelegation]) ??
(model[MySqlAnnotationNames.CharSetDelegation] is bool explicitlyDelegateToChildren
? explicitlyDelegateToChildren
@@ -153,7 +163,7 @@ public static void SetCharSetDelegation([NotNull] this IMutableModel model, Dele
///
/// The model.
/// The actual character set delegation modes.
- public static DelegationModes GetActualCharSetDelegation([NotNull] this IModel model)
+ public static DelegationModes GetActualCharSetDelegation([NotNull] this IReadOnlyModel model)
{
var delegationModes = model.GetCharSetDelegation() ?? DelegationModes.Default;
return delegationModes == DelegationModes.Default
@@ -170,7 +180,7 @@ public static DelegationModes GetActualCharSetDelegation([NotNull] this IModel m
///
/// The model.
/// The collation delegation modes.
- public static DelegationModes? GetCollationDelegation([NotNull] this IModel model)
+ public static DelegationModes? GetCollationDelegation([NotNull] this IReadOnlyModel model)
=> ObjectToEnumConverter.GetEnumValue(model[MySqlAnnotationNames.CollationDelegation]) ??
(model[MySqlAnnotationNames.CollationDelegation] is bool explicitlyDelegateToChildren
? explicitlyDelegateToChildren
@@ -219,7 +229,7 @@ public static void SetCollationDelegation([NotNull] this IMutableModel model, De
///
/// The model.
/// The actual collation delegation modes.
- public static DelegationModes GetActualCollationDelegation([NotNull] this IModel model)
+ public static DelegationModes GetActualCollationDelegation([NotNull] this IReadOnlyModel model)
{
var delegationModes = model.GetCollationDelegation() ?? DelegationModes.Default;
return delegationModes == DelegationModes.Default
@@ -240,7 +250,7 @@ public static DelegationModes GetActualCollationDelegation([NotNull] this IModel
/// An empty string means that no explicit collation will be applied, while means that the default
/// collation `ascii_general_ci` will be applied.
///
- public static string GetGuidCollation([NotNull] this IModel model)
+ public static string GetGuidCollation([NotNull] this IReadOnlyModel model)
=> model[MySqlAnnotationNames.GuidCollation] as string;
///
@@ -288,7 +298,7 @@ public static string SetGuidCollation([NotNull] this IConventionModel model, str
///
/// if no collation should be set, otherwise the concrete collation to apply.
///
- public static string GetActualGuidCollation([NotNull] this IModel model, [CanBeNull] string defaultCollation)
+ public static string GetActualGuidCollation([NotNull] this IReadOnlyModel model, [CanBeNull] string defaultCollation)
=> model.GetGuidCollation() switch
{
null => defaultCollation,
diff --git a/src/EFCore.MySql/Extensions/MySqlPropertyExtensions.cs b/src/EFCore.MySql/Extensions/MySqlPropertyExtensions.cs
index c99d0f939..465a76332 100644
--- a/src/EFCore.MySql/Extensions/MySqlPropertyExtensions.cs
+++ b/src/EFCore.MySql/Extensions/MySqlPropertyExtensions.cs
@@ -2,10 +2,12 @@
// Licensed under the MIT. See LICENSE in the project root for license information.
using System;
+using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Pomelo.EntityFrameworkCore.MySql.Internal;
using Pomelo.EntityFrameworkCore.MySql.Metadata.Internal;
@@ -28,45 +30,168 @@ public static class MySqlPropertyExtensions
///
///
/// The strategy, or if none was set.
- public static MySqlValueGenerationStrategy? GetValueGenerationStrategy([NotNull] this IReadOnlyProperty property, StoreObjectIdentifier storeObject = default)
+ public static MySqlValueGenerationStrategy GetValueGenerationStrategy([NotNull] this IReadOnlyProperty property)
{
- var annotation = property[MySqlAnnotationNames.ValueGenerationStrategy];
- if (annotation != null)
+ // Allow users to use the underlying type value instead of the enum itself.
+ // Workaround for: https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/issues/1205
+ if (property[MySqlAnnotationNames.ValueGenerationStrategy] is { } annotationValue &&
+ ObjectToEnumConverter.GetEnumValue(annotationValue) is { } enumValue)
{
- // Allow users to use the underlying type value instead of the enum itself.
- // Workaround for: https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/issues/1205
- //return ObjectToEnumConverter.GetEnumValue(annotation);
- return ObjectToEnumConverter.GetEnumValue(annotation);
+ return enumValue;
}
- if (property.GetContainingForeignKeys().Any(fk => !fk.IsBaseLinking()) ||
- property.TryGetDefaultValue(storeObject, out _) ||
- property.GetDefaultValueSql() != null ||
- property.GetComputedColumnSql() != null)
+ if (property.ValueGenerated == ValueGenerated.OnAdd)
{
- return null;
+ if (property.IsForeignKey()
+ || property.TryGetDefaultValue(out _)
+ || property.GetDefaultValueSql() != null
+ || property.GetComputedColumnSql() != null)
+ {
+ return MySqlValueGenerationStrategy.None;
+ }
+
+ if (IsCompatibleIdentityColumn(property))
+ {
+ return MySqlValueGenerationStrategy.IdentityColumn;
+ }
+
+ return GetDefaultValueGenerationStrategy(property);
}
- if (storeObject != default &&
- property.ValueGenerated == ValueGenerated.Never)
+ if (property.ValueGenerated == ValueGenerated.OnAddOrUpdate)
{
- return property.FindSharedStoreObjectRootProperty(storeObject)
- ?.GetValueGenerationStrategy(storeObject);
+ if (IsCompatibleComputedColumn(property))
+ {
+ return MySqlValueGenerationStrategy.ComputedColumn;
+ }
}
- if (property.ValueGenerated == ValueGenerated.OnAdd &&
- IsCompatibleIdentityColumn(property))
+ return MySqlValueGenerationStrategy.None;
+ }
+
+ public static MySqlValueGenerationStrategy GetValueGenerationStrategy(
+ this IReadOnlyProperty property,
+ in StoreObjectIdentifier storeObject)
+ => GetValueGenerationStrategy(property, storeObject, null);
+
+ internal static MySqlValueGenerationStrategy GetValueGenerationStrategy(
+ this IReadOnlyProperty property,
+ in StoreObjectIdentifier storeObject,
+ [CanBeNull] ITypeMappingSource typeMappingSource)
+ {
+ if (property.FindOverrides(storeObject)?.FindAnnotation(MySqlAnnotationNames.ValueGenerationStrategy) is { } @override)
{
- return MySqlValueGenerationStrategy.IdentityColumn;
+ return ObjectToEnumConverter.GetEnumValue(@override.Value) ?? MySqlValueGenerationStrategy.None;
}
- if (property.ValueGenerated == ValueGenerated.OnAddOrUpdate &&
- IsCompatibleComputedColumn(property))
+ var annotation = property.FindAnnotation(MySqlAnnotationNames.ValueGenerationStrategy);
+ if (annotation?.Value is { } annotationValue
+ && ObjectToEnumConverter.GetEnumValue(annotationValue) is { } enumValue
+ && StoreObjectIdentifier.Create(property.DeclaringEntityType, storeObject.StoreObjectType) == storeObject)
{
- return MySqlValueGenerationStrategy.ComputedColumn;
+ return enumValue;
}
- return null;
+ var table = storeObject;
+ var sharedTableRootProperty = property.FindSharedStoreObjectRootProperty(storeObject);
+ if (sharedTableRootProperty != null)
+ {
+ return sharedTableRootProperty.GetValueGenerationStrategy(storeObject, typeMappingSource)
+ == MySqlValueGenerationStrategy.IdentityColumn
+ && table.StoreObjectType == StoreObjectType.Table
+ && !property.GetContainingForeignKeys().Any(
+ fk =>
+ !fk.IsBaseLinking()
+ || (StoreObjectIdentifier.Create(fk.PrincipalEntityType, StoreObjectType.Table)
+ is StoreObjectIdentifier principal
+ && fk.GetConstraintName(table, principal) != null))
+ ? MySqlValueGenerationStrategy.IdentityColumn
+ : MySqlValueGenerationStrategy.None;
+ }
+
+ if (property.ValueGenerated == ValueGenerated.OnAdd)
+ {
+ if (table.StoreObjectType != StoreObjectType.Table
+ || property.TryGetDefaultValue(storeObject, out _)
+ || property.GetDefaultValueSql(storeObject) != null
+ || property.GetComputedColumnSql(storeObject) != null
+ || property.GetContainingForeignKeys()
+ .Any(
+ fk =>
+ !fk.IsBaseLinking()
+ || (StoreObjectIdentifier.Create(fk.PrincipalEntityType, StoreObjectType.Table)
+ is StoreObjectIdentifier principal
+ && fk.GetConstraintName(table, principal) != null)))
+ {
+ return MySqlValueGenerationStrategy.None;
+ }
+
+ if (IsCompatibleIdentityColumn(property))
+ {
+ return MySqlValueGenerationStrategy.IdentityColumn;
+ }
+
+ var defaultStrategy = GetDefaultValueGenerationStrategy(property, storeObject, typeMappingSource);
+ if (defaultStrategy != MySqlValueGenerationStrategy.None)
+ {
+ if (annotation != null)
+ {
+ return (MySqlValueGenerationStrategy?)annotation.Value ?? MySqlValueGenerationStrategy.None;
+ }
+ }
+
+ return defaultStrategy;
+ }
+
+ if (property.ValueGenerated == ValueGenerated.OnAddOrUpdate)
+ {
+ if (IsCompatibleComputedColumn(property))
+ {
+ return MySqlValueGenerationStrategy.ComputedColumn;
+ }
+ }
+
+ return MySqlValueGenerationStrategy.None;
+ }
+
+ ///
+ /// Returns the to use for the property.
+ ///
+ ///
+ /// If no strategy is set for the property, then the strategy to use will be taken from the .
+ ///
+ /// The property overrides.
+ /// The strategy, or if none was set.
+ public static MySqlValueGenerationStrategy? GetValueGenerationStrategy(this IReadOnlyRelationalPropertyOverrides overrides)
+ => overrides.FindAnnotation(MySqlAnnotationNames.ValueGenerationStrategy) is { } @override
+ ? ObjectToEnumConverter.GetEnumValue(@override.Value) ??
+ MySqlValueGenerationStrategy.None
+ : null;
+
+ private static MySqlValueGenerationStrategy GetDefaultValueGenerationStrategy(IReadOnlyProperty property)
+ {
+ var modelStrategy = property.DeclaringEntityType.Model.GetValueGenerationStrategy();
+
+ if (modelStrategy == MySqlValueGenerationStrategy.IdentityColumn &&
+ IsCompatibleAutoIncrementColumn(property))
+ {
+ return MySqlValueGenerationStrategy.IdentityColumn;
+ }
+
+ return MySqlValueGenerationStrategy.None;
+ }
+
+ private static MySqlValueGenerationStrategy GetDefaultValueGenerationStrategy(
+ IReadOnlyProperty property,
+ in StoreObjectIdentifier storeObject,
+ [CanBeNull] ITypeMappingSource typeMappingSource)
+ {
+ var modelStrategy = property.DeclaringEntityType.Model.GetValueGenerationStrategy();
+
+ return modelStrategy == MySqlValueGenerationStrategy.IdentityColumn
+ && IsCompatibleAutoIncrementColumn(property, storeObject, typeMappingSource)
+ ? MySqlValueGenerationStrategy.IdentityColumn
+ : MySqlValueGenerationStrategy.None;
}
///
@@ -75,12 +200,11 @@ public static class MySqlPropertyExtensions
/// The property.
/// The strategy to use.
public static void SetValueGenerationStrategy(
- [NotNull] this IMutableProperty property, MySqlValueGenerationStrategy? value)
- {
- CheckValueGenerationStrategy(property, value);
-
- property.SetOrRemoveAnnotation(MySqlAnnotationNames.ValueGenerationStrategy, value);
- }
+ [NotNull] this IMutableProperty property,
+ MySqlValueGenerationStrategy? value)
+ => property.SetOrRemoveAnnotation(
+ MySqlAnnotationNames.ValueGenerationStrategy,
+ CheckValueGenerationStrategy(property, value));
///
/// Sets the to use for the property.
@@ -88,45 +212,128 @@ public static void SetValueGenerationStrategy(
/// The property.
/// The strategy to use.
/// Indicates whether the configuration was specified using a data annotation.
- public static MySqlValueGenerationStrategy? SetValueGenerationStrategy([NotNull] this IConventionProperty property, MySqlValueGenerationStrategy? value, bool fromDataAnnotation = false)
- {
- CheckValueGenerationStrategy(property, value);
+ public static MySqlValueGenerationStrategy? SetValueGenerationStrategy(
+ [NotNull] this IConventionProperty property,
+ MySqlValueGenerationStrategy? value,
+ bool fromDataAnnotation = false)
+ => (MySqlValueGenerationStrategy?)property.SetOrRemoveAnnotation(
+ MySqlAnnotationNames.ValueGenerationStrategy,
+ CheckValueGenerationStrategy(property, value),
+ fromDataAnnotation)
+ ?.Value;
- property.SetOrRemoveAnnotation(MySqlAnnotationNames.ValueGenerationStrategy, value, fromDataAnnotation);
+ ///
+ /// Sets the to use for the property for a particular table.
+ ///
+ /// The property.
+ /// The strategy to use.
+ /// The identifier of the table containing the column.
+ public static void SetValueGenerationStrategy(
+ this IMutableProperty property,
+ MySqlValueGenerationStrategy? value,
+ in StoreObjectIdentifier storeObject)
+ => property.GetOrCreateOverrides(storeObject)
+ .SetValueGenerationStrategy(value);
- return value;
- }
+ ///
+ /// Sets the to use for the property for a particular table.
+ ///
+ /// The property.
+ /// The strategy to use.
+ /// The identifier of the table containing the column.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// The configured value.
+ public static MySqlValueGenerationStrategy? SetValueGenerationStrategy(
+ this IConventionProperty property,
+ MySqlValueGenerationStrategy? value,
+ in StoreObjectIdentifier storeObject,
+ bool fromDataAnnotation = false)
+ => property.GetOrCreateOverrides(storeObject, fromDataAnnotation)
+ .SetValueGenerationStrategy(value, fromDataAnnotation);
///
- /// Returns the for the .
+ /// Sets the to use for the property for a particular table.
+ ///
+ /// The property overrides.
+ /// The strategy to use.
+ public static void SetValueGenerationStrategy(
+ this IMutableRelationalPropertyOverrides overrides,
+ MySqlValueGenerationStrategy? value)
+ => overrides.SetOrRemoveAnnotation(
+ MySqlAnnotationNames.ValueGenerationStrategy,
+ CheckValueGenerationStrategy(overrides.Property, value));
+
+ ///
+ /// Sets the to use for the property for a particular table.
+ ///
+ /// The property overrides.
+ /// The strategy to use.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// The configured value.
+ public static MySqlValueGenerationStrategy? SetValueGenerationStrategy(
+ this IConventionRelationalPropertyOverrides overrides,
+ MySqlValueGenerationStrategy? value,
+ bool fromDataAnnotation = false)
+ => (MySqlValueGenerationStrategy?)overrides.SetOrRemoveAnnotation(
+ MySqlAnnotationNames.ValueGenerationStrategy,
+ CheckValueGenerationStrategy(overrides.Property, value),
+ fromDataAnnotation)?.Value;
+
+ ///
+ /// Returns the for the .
///
/// The property.
/// The for the .
- public static ConfigurationSource? GetValueGenerationStrategyConfigurationSource(this IConventionProperty property)
+ public static ConfigurationSource? GetValueGenerationStrategyConfigurationSource(
+ this IConventionProperty property)
=> property.FindAnnotation(MySqlAnnotationNames.ValueGenerationStrategy)?.GetConfigurationSource();
- private static void CheckValueGenerationStrategy(IReadOnlyProperty property, MySqlValueGenerationStrategy? value)
+ ///
+ /// Returns the for the for a particular table.
+ ///
+ /// The property.
+ /// The identifier of the table containing the column.
+ /// The for the .
+ public static ConfigurationSource? GetValueGenerationStrategyConfigurationSource(
+ this IConventionProperty property,
+ in StoreObjectIdentifier storeObject)
+ => property.FindOverrides(storeObject)?.GetValueGenerationStrategyConfigurationSource();
+
+ ///
+ /// Returns the for the for a particular table.
+ ///
+ /// The property overrides.
+ /// The for the .
+ public static ConfigurationSource? GetValueGenerationStrategyConfigurationSource(
+ this IConventionRelationalPropertyOverrides overrides)
+ => overrides.FindAnnotation(MySqlAnnotationNames.ValueGenerationStrategy)?.GetConfigurationSource();
+
+ private static MySqlValueGenerationStrategy? CheckValueGenerationStrategy(IReadOnlyProperty property, MySqlValueGenerationStrategy? value)
{
- if (value != null)
+ if (value == null)
{
- var propertyType = property.ClrType;
+ return null;
+ }
- if (value == MySqlValueGenerationStrategy.IdentityColumn
- && !IsCompatibleIdentityColumn(property))
- {
- throw new ArgumentException(
- MySqlStrings.IdentityBadType(
- property.Name, property.DeclaringEntityType.DisplayName(), propertyType.ShortDisplayName()));
- }
+ var propertyType = property.ClrType;
- if (value == MySqlValueGenerationStrategy.ComputedColumn
- && !IsCompatibleComputedColumn(property))
- {
- throw new ArgumentException(
- MySqlStrings.ComputedBadType(
- property.Name, property.DeclaringEntityType.DisplayName(), propertyType.ShortDisplayName()));
- }
+ if (value == MySqlValueGenerationStrategy.IdentityColumn
+ && !IsCompatibleIdentityColumn(property))
+ {
+ throw new ArgumentException(
+ MySqlStrings.IdentityBadType(
+ property.Name, property.DeclaringEntityType.DisplayName(), propertyType.ShortDisplayName()));
+ }
+
+ if (value == MySqlValueGenerationStrategy.ComputedColumn
+ && !IsCompatibleComputedColumn(property))
+ {
+ throw new ArgumentException(
+ MySqlStrings.ComputedBadType(
+ property.Name, property.DeclaringEntityType.DisplayName(), propertyType.ShortDisplayName()));
}
+
+ return value;
}
///
@@ -138,6 +345,13 @@ public static bool IsCompatibleIdentityColumn(IReadOnlyProperty property)
=> IsCompatibleAutoIncrementColumn(property) ||
IsCompatibleCurrentTimestampColumn(property);
+ private static bool IsCompatibleIdentityColumn(
+ IReadOnlyProperty property,
+ in StoreObjectIdentifier storeObject,
+ [CanBeNull] ITypeMappingSource typeMappingSource)
+ => IsCompatibleAutoIncrementColumn(property, storeObject, typeMappingSource) ||
+ IsCompatibleCurrentTimestampColumn(property, storeObject, typeMappingSource);
+
///
/// Returns a value indicating whether the property is compatible with an `AUTO_INCREMENT` column.
///
@@ -151,6 +365,23 @@ public static bool IsCompatibleAutoIncrementColumn(IReadOnlyProperty property)
type == typeof(decimal);
}
+ private static bool IsCompatibleAutoIncrementColumn(
+ IReadOnlyProperty property,
+ in StoreObjectIdentifier storeObject,
+ [CanBeNull] ITypeMappingSource typeMappingSource)
+ {
+ if (storeObject.StoreObjectType != StoreObjectType.Table)
+ {
+ return false;
+ }
+
+ var valueConverter = GetConverter(property, storeObject, typeMappingSource);
+ var type = (valueConverter?.ProviderClrType ?? property.ClrType).UnwrapNullableType();
+
+ return (type.IsInteger()
+ || type == typeof(decimal));
+ }
+
///
/// Returns a value indicating whether the property is compatible with a `CURRENT_TIMESTAMP` column default.
///
@@ -164,6 +395,23 @@ public static bool IsCompatibleCurrentTimestampColumn(IReadOnlyProperty property
type == typeof(DateTimeOffset);
}
+ private static bool IsCompatibleCurrentTimestampColumn(
+ IReadOnlyProperty property,
+ in StoreObjectIdentifier storeObject,
+ [CanBeNull] ITypeMappingSource typeMappingSource)
+ {
+ if (storeObject.StoreObjectType != StoreObjectType.Table)
+ {
+ return false;
+ }
+
+ var valueConverter = GetConverter(property, storeObject, typeMappingSource);
+ var type = (valueConverter?.ProviderClrType ?? property.ClrType).UnwrapNullableType();
+
+ return type == typeof(DateTime) ||
+ type == typeof(DateTimeOffset);
+ }
+
///
/// Returns a value indicating whether the property is compatible with .
///
@@ -188,7 +436,16 @@ private static bool HasExternalConverter(IReadOnlyProperty property)
}
private static ValueConverter GetConverter(IReadOnlyProperty property)
- => property.FindTypeMapping()?.Converter ?? property.GetValueConverter();
+ => property.GetValueConverter() ??
+ property.FindTypeMapping()?.Converter;
+
+ private static ValueConverter GetConverter(
+ IReadOnlyProperty property,
+ StoreObjectIdentifier storeObject,
+ [CanBeNull] ITypeMappingSource typeMappingSource)
+ => property.GetValueConverter()
+ ?? (property.FindRelationalTypeMapping(storeObject)
+ ?? typeMappingSource?.FindMapping((IProperty)property))?.Converter;
///
/// Returns the name of the charset used by the column of the property.
diff --git a/src/EFCore.MySql/Extensions/MySqlServiceCollectionExtensions.cs b/src/EFCore.MySql/Extensions/MySqlServiceCollectionExtensions.cs
index 830d25cc9..57d0ff134 100644
--- a/src/EFCore.MySql/Extensions/MySqlServiceCollectionExtensions.cs
+++ b/src/EFCore.MySql/Extensions/MySqlServiceCollectionExtensions.cs
@@ -1,6 +1,7 @@
// Copyright (c) Pomelo Foundation. All rights reserved.
// Licensed under the MIT. See LICENSE in the project root for license information.
+using System;
using Pomelo.EntityFrameworkCore.MySql.Infrastructure.Internal;
using Pomelo.EntityFrameworkCore.MySql.Internal;
using Pomelo.EntityFrameworkCore.MySql.Migrations.Internal;
@@ -8,6 +9,7 @@
using Pomelo.EntityFrameworkCore.MySql.Update.Internal;
using Pomelo.EntityFrameworkCore.MySql.ValueGeneration.Internal;
using JetBrains.Annotations;
+using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -20,16 +22,77 @@
using Pomelo.EntityFrameworkCore.MySql.Diagnostics.Internal;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Metadata;
+using Pomelo.EntityFrameworkCore.MySql.Infrastructure;
using Pomelo.EntityFrameworkCore.MySql.Metadata.Internal;
using Pomelo.EntityFrameworkCore.MySql.Migrations;
using Pomelo.EntityFrameworkCore.MySql.Query.ExpressionVisitors.Internal;
using Pomelo.EntityFrameworkCore.MySql.Query.Internal;
+using Pomelo.EntityFrameworkCore.MySql.Storage;
// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection
{
public static class MySqlServiceCollectionExtensions
{
+ ///
+ ///
+ /// Registers the given Entity Framework context as a service in the
+ /// and configures it to connect to a MySQL compatible database.
+ ///
+ ///
+ /// Use this method when using dependency injection in your application, such as with ASP.NET Core.
+ /// For applications that don't use dependency injection, consider creating
+ /// instances directly with its constructor. The method can then be
+ /// overridden to configure the Pomelo.EntityFrameworkCore.MySql provider and connection string.
+ ///
+ ///
+ /// To configure the for the context, either override the
+ /// method in your derived context, or supply
+ /// an optional action to configure the for the context.
+ ///
+ ///
+ /// For more information on how to use this method, see the Entity Framework Core documentation at https://aka.ms/efdocs.
+ /// For more information on using dependency injection, see https://go.microsoft.com/fwlink/?LinkId=526890.
+ ///
+ ///
+ /// The type of context to be registered.
+ /// The to add services to.
+ /// The connection string of the database to connect to.
+ ///
+ ///
+ /// The version of the database server.
+ ///
+ ///
+ /// Create an object for this parameter by calling the static method
+ /// ,
+ /// by calling the static method (which retrieves the server version directly
+ /// from the database server),
+ /// by parsing a version string using the static methods
+ /// or ,
+ /// or by directly instantiating an object from the (for MySQL) or
+ /// (for MariaDB) classes.
+ ///
+ ///
+ /// An optional action to allow additional MySQL specific configuration.
+ /// An optional action to configure the for the context.
+ /// The same service collection so that multiple calls can be chained.
+ public static IServiceCollection AddMySql(
+ this IServiceCollection serviceCollection,
+ string connectionString,
+ ServerVersion serverVersion,
+ Action mySqlOptionsAction = null,
+ Action optionsAction = null)
+ where TContext : DbContext
+ {
+ Check.NotNull(serviceCollection, nameof(serviceCollection));
+
+ return serviceCollection.AddDbContext((_, options) =>
+ {
+ optionsAction?.Invoke(options);
+ options.UseMySql(connectionString, serverVersion, mySqlOptionsAction);
+ });
+ }
+
public static IServiceCollection AddEntityFrameworkMySql([NotNull] this IServiceCollection serviceCollection)
{
Check.NotNull(serviceCollection, nameof(serviceCollection));
@@ -45,6 +108,7 @@ public static IServiceCollection AddEntityFrameworkMySql([NotNull] this IService
.TryAdd()
//.TryAdd() // What is that?
.TryAdd()
+ .TryAdd()
.TryAdd()
.TryAdd()
.TryAdd(p => p.GetService())
@@ -53,6 +117,7 @@ public static IServiceCollection AddEntityFrameworkMySql([NotNull] this IService
.TryAdd()
.TryAdd()
.TryAdd()
+ .TryAdd()
.TryAdd()
.TryAdd()
.TryAdd()
diff --git a/src/EFCore.MySql/Infrastructure/MariaDbServerVersion.cs b/src/EFCore.MySql/Infrastructure/MariaDbServerVersion.cs
index 9db204ad9..b1b776083 100644
--- a/src/EFCore.MySql/Infrastructure/MariaDbServerVersion.cs
+++ b/src/EFCore.MySql/Infrastructure/MariaDbServerVersion.cs
@@ -82,6 +82,8 @@ internal MariaDbServerVersionSupport([NotNull] ServerVersion serverVersion)
public override bool InformationSchemaCheckConstraintsTable => ServerVersion.Version >= new Version(10, 3, 10) ||
ServerVersion.Version.Major == 10 && ServerVersion.Version.Minor == 2 && ServerVersion.Version.Build >= 22; // MySQL is missing the explicit TABLE_NAME column that MariaDB supports, so always join the TABLE_CONSTRAINTS table when accessing CHECK_CONSTRAINTS for any database server that supports CHECK_CONSTRAINTS.
public override bool IdentifyJsonColumsByCheckConstraints => true;
+ public override bool Returning => ServerVersion.Version >= new Version(10, 5, 0);
+ public override bool CommonTableExpressions => ServerVersion.Version >= new Version(10, 2, 1);
}
}
}
diff --git a/src/EFCore.MySql/Infrastructure/MySqlServerVersion.cs b/src/EFCore.MySql/Infrastructure/MySqlServerVersion.cs
index d88fa4c43..81c79ae7e 100644
--- a/src/EFCore.MySql/Infrastructure/MySqlServerVersion.cs
+++ b/src/EFCore.MySql/Infrastructure/MySqlServerVersion.cs
@@ -85,6 +85,8 @@ internal MySqlServerVersionSupport([NotNull] ServerVersion serverVersion)
public override bool FullTextParser => ServerVersion.Version >= new Version(5, 7, 3);
public override bool InformationSchemaCheckConstraintsTable => ServerVersion.Version >= new Version(8, 0, 16); // MySQL is missing the explicit TABLE_NAME column that MariaDB supports, so always join the TABLE_CONSTRAINTS table when accessing CHECK_CONSTRAINTS for any database server that supports CHECK_CONSTRAINTS.
public override bool MySqlBugLimit0Offset0ExistsWorkaround => true;
+ public override bool DescendingIndexes => ServerVersion.Version >= new Version(8, 0, 1);
+ public override bool CommonTableExpressions => ServerVersion.Version >= new Version(8, 0, 1);
}
}
}
diff --git a/src/EFCore.MySql/Infrastructure/ServerVersionSupport.cs b/src/EFCore.MySql/Infrastructure/ServerVersionSupport.cs
index 0d0acb3f9..c595770c3 100644
--- a/src/EFCore.MySql/Infrastructure/ServerVersionSupport.cs
+++ b/src/EFCore.MySql/Infrastructure/ServerVersionSupport.cs
@@ -84,5 +84,8 @@ public virtual bool PropertyOrVersion(string propertyNameOrServerVersion)
public virtual bool InformationSchemaCheckConstraintsTable => false;
public virtual bool IdentifyJsonColumsByCheckConstraints => false;
public virtual bool MySqlBugLimit0Offset0ExistsWorkaround => false;
+ public virtual bool DescendingIndexes => false;
+ public virtual bool Returning => false;
+ public virtual bool CommonTableExpressions => false;
}
}
diff --git a/src/EFCore.MySql/Internal/MySqlModelValidator.cs b/src/EFCore.MySql/Internal/MySqlModelValidator.cs
index 0853c60d0..005b57069 100644
--- a/src/EFCore.MySql/Internal/MySqlModelValidator.cs
+++ b/src/EFCore.MySql/Internal/MySqlModelValidator.cs
@@ -1,6 +1,8 @@
// Copyright (c) Pomelo Foundation. All rights reserved.
// Licensed under the MIT. See LICENSE in the project root for license information.
+using System;
+using System.Linq;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
@@ -38,15 +40,50 @@ public MySqlModelValidator(
{
}
- ///
- /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
- /// the same compatibility standards as public APIs. It may be changed or removed without notice in
- /// any release. You should only use it directly in your code with extreme caution and knowing that
- /// doing so can result in application failures when updating to a new Entity Framework Core release.
- ///
- public override void Validate(IModel model, IDiagnosticsLogger logger)
+ ///
+ protected override void ValidateStoredProcedures(
+ IModel model,
+ IDiagnosticsLogger logger)
{
- base.Validate(model, logger);
+ base.ValidateStoredProcedures(model, logger);
+
+ foreach (var entityType in model.GetEntityTypes())
+ {
+ if (entityType.GetDeleteStoredProcedure() is { } deleteStoredProcedure)
+ {
+ ValidateSproc(deleteStoredProcedure, logger);
+ }
+
+ if (entityType.GetInsertStoredProcedure() is { } insertStoredProcedure)
+ {
+ ValidateSproc(insertStoredProcedure, logger);
+ }
+
+ if (entityType.GetUpdateStoredProcedure() is { } updateStoredProcedure)
+ {
+ ValidateSproc(updateStoredProcedure, logger);
+ }
+ }
+
+ static void ValidateSproc(IStoredProcedure sproc, IDiagnosticsLogger logger)
+ {
+ var entityType = sproc.EntityType;
+ var storeObjectIdentifier = sproc.GetStoreIdentifier();
+
+ if (sproc.ResultColumns.Any())
+ {
+ throw new InvalidOperationException(MySqlStrings.StoredProcedureResultColumnsNotSupported(
+ entityType.DisplayName(),
+ storeObjectIdentifier.DisplayName()));
+ }
+
+ if (sproc.IsRowsAffectedReturned)
+ {
+ throw new InvalidOperationException(MySqlStrings.StoredProcedureReturnValueNotSupported(
+ entityType.DisplayName(),
+ storeObjectIdentifier.DisplayName()));
+ }
+ }
}
}
}
diff --git a/src/EFCore.MySql/Metadata/Conventions/MySqlValueGenerationConvention.cs b/src/EFCore.MySql/Metadata/Conventions/MySqlValueGenerationConvention.cs
index c61137cbd..91aa2f788 100644
--- a/src/EFCore.MySql/Metadata/Conventions/MySqlValueGenerationConvention.cs
+++ b/src/EFCore.MySql/Metadata/Conventions/MySqlValueGenerationConvention.cs
@@ -1,12 +1,14 @@
// Copyright (c) Pomelo Foundation. All rights reserved.
// Licensed under the MIT. See LICENSE in the project root for license information.
+using System.Linq;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage;
using Pomelo.EntityFrameworkCore.MySql.Metadata.Internal;
namespace Pomelo.EntityFrameworkCore.MySql.Metadata.Conventions
@@ -62,13 +64,15 @@ public override void ProcessPropertyAnnotationChanged(
/// The store value generation strategy to set for the given property.
protected override ValueGenerated? GetValueGenerated(IConventionProperty property)
{
- var tableName = property.DeclaringEntityType.GetTableName();
- if (tableName == null)
+ var declaringTable = property.GetMappedStoreObjects(StoreObjectType.Table).FirstOrDefault();
+ if (declaringTable.Name == null)
{
return null;
}
- return GetValueGenerated(property, StoreObjectIdentifier.Table(tableName, property.DeclaringEntityType.GetSchema()));
+ // If the first mapping can be value generated then we'll consider all mappings to be value generated
+ // as this is a client-side configuration and can't be specified per-table.
+ return GetValueGenerated(property, declaringTable, Dependencies.TypeMappingSource);
}
///
@@ -77,7 +81,9 @@ public override void ProcessPropertyAnnotationChanged(
/// The property.
/// The identifier of the store object.
/// The store value generation strategy to set for the given property.
- public static new ValueGenerated? GetValueGenerated([NotNull] IReadOnlyProperty property, in StoreObjectIdentifier storeObject)
+ public static new ValueGenerated? GetValueGenerated(
+ [NotNull] IReadOnlyProperty property,
+ in StoreObjectIdentifier storeObject)
{
var valueGenerated = RelationalValueGenerationConvention.GetValueGenerated(property, storeObject);
if (valueGenerated != null)
@@ -85,19 +91,31 @@ public override void ProcessPropertyAnnotationChanged(
return valueGenerated;
}
- var valueGenerationStrategy = property.GetValueGenerationStrategy(storeObject);
- if (valueGenerationStrategy.HasValue)
+ return property.GetValueGenerationStrategy(storeObject) switch
{
- switch (valueGenerationStrategy.Value)
- {
- case MySqlValueGenerationStrategy.IdentityColumn:
- return ValueGenerated.OnAdd;
- case MySqlValueGenerationStrategy.ComputedColumn:
- return ValueGenerated.OnAddOrUpdate;
- }
+ MySqlValueGenerationStrategy.IdentityColumn => ValueGenerated.OnAdd,
+ MySqlValueGenerationStrategy.ComputedColumn => ValueGenerated.OnAddOrUpdate,
+ _ => null
+ };
+ }
+
+ private ValueGenerated? GetValueGenerated(
+ IReadOnlyProperty property,
+ in StoreObjectIdentifier storeObject,
+ ITypeMappingSource typeMappingSource)
+ {
+ var valueGenerated = RelationalValueGenerationConvention.GetValueGenerated(property, storeObject);
+ if (valueGenerated != null)
+ {
+ return valueGenerated;
}
- return null;
+ return property.GetValueGenerationStrategy(storeObject, typeMappingSource) switch
+ {
+ MySqlValueGenerationStrategy.IdentityColumn => ValueGenerated.OnAdd,
+ MySqlValueGenerationStrategy.ComputedColumn => ValueGenerated.OnAddOrUpdate,
+ _ => null
+ };
}
}
}
diff --git a/src/EFCore.MySql/Metadata/Internal/MySqlAnnotationProvider.cs b/src/EFCore.MySql/Metadata/Internal/MySqlAnnotationProvider.cs
index 0d1202030..bb27141ce 100644
--- a/src/EFCore.MySql/Metadata/Internal/MySqlAnnotationProvider.cs
+++ b/src/EFCore.MySql/Metadata/Internal/MySqlAnnotationProvider.cs
@@ -165,7 +165,7 @@ public override IEnumerable For(IColumn column, bool designTime)
var properties = column.PropertyMappings.Select(m => m.Property).ToArray();
if (column.PropertyMappings.Where(
- m => m.TableMapping.IsSharedTablePrincipal &&
+ m => (m.TableMapping.IsSharedTablePrincipal ?? true) &&
m.TableMapping.EntityType == m.Property.DeclaringEntityType)
.Select(m => m.Property)
.FirstOrDefault(p => p.GetValueGenerationStrategy(table) == MySqlValueGenerationStrategy.IdentityColumn) is IProperty identityProperty)
diff --git a/src/EFCore.MySql/Migrations/Internal/MySqlHistoryRepository.cs b/src/EFCore.MySql/Migrations/Internal/MySqlHistoryRepository.cs
index 335b665e6..e77348d72 100644
--- a/src/EFCore.MySql/Migrations/Internal/MySqlHistoryRepository.cs
+++ b/src/EFCore.MySql/Migrations/Internal/MySqlHistoryRepository.cs
@@ -165,14 +165,14 @@ protected override string MigrationIdColumnName
=> _migrationIdColumnName ??= EnsureModel()
.FindEntityType(typeof(HistoryRow))!
.FindProperty(nameof(HistoryRow.MigrationId))!
- .GetColumnBaseName();
+ .GetColumnName();
// Original implementation.
protected override string ProductVersionColumnName
=> _productVersionColumnName ??= EnsureModel()
.FindEntityType(typeof(HistoryRow))!
.FindProperty(nameof(HistoryRow.ProductVersion))!
- .GetColumnBaseName();
+ .GetColumnName();
#endregion Necessary implementation because we cannot directly override EnsureModel
}
diff --git a/src/EFCore.MySql/Migrations/Internal/MySqlMigrationsModelDiffer.cs b/src/EFCore.MySql/Migrations/Internal/MySqlMigrationsModelDiffer.cs
index 233c34734..63f97c0c6 100644
--- a/src/EFCore.MySql/Migrations/Internal/MySqlMigrationsModelDiffer.cs
+++ b/src/EFCore.MySql/Migrations/Internal/MySqlMigrationsModelDiffer.cs
@@ -7,15 +7,12 @@
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
-using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations.Internal;
using Microsoft.EntityFrameworkCore.Migrations.Operations;
using Microsoft.EntityFrameworkCore.Storage;
-using Microsoft.EntityFrameworkCore.Update;
using Microsoft.EntityFrameworkCore.Update.Internal;
using Pomelo.EntityFrameworkCore.MySql.Internal;
using Pomelo.EntityFrameworkCore.MySql.Metadata.Internal;
@@ -33,16 +30,14 @@ protected static class InternalLocalAnnotationNames
}
public MySqlMigrationsModelDiffer(
- [NotNull] IRelationalTypeMappingSource typeMappingSource,
- [NotNull] IMigrationsAnnotationProvider migrationsAnnotations,
- [NotNull] IChangeDetector changeDetector,
- [NotNull] IUpdateAdapterFactory updateAdapterFactory,
- [NotNull] CommandBatchPreparerDependencies commandBatchPreparerDependencies)
+ IRelationalTypeMappingSource typeMappingSource,
+ IMigrationsAnnotationProvider migrationsAnnotationProvider,
+ IRowIdentityMapFactory rowIdentityMapFactory,
+ CommandBatchPreparerDependencies commandBatchPreparerDependencies)
: base(
typeMappingSource,
- migrationsAnnotations,
- changeDetector,
- updateAdapterFactory,
+ migrationsAnnotationProvider,
+ rowIdentityMapFactory,
commandBatchPreparerDependencies)
{
AssertAllMigrationOperationProperties();
diff --git a/src/EFCore.MySql/Migrations/MySqlMigrationsSqlGenerator.cs b/src/EFCore.MySql/Migrations/MySqlMigrationsSqlGenerator.cs
index cdf892234..ac08ae455 100644
--- a/src/EFCore.MySql/Migrations/MySqlMigrationsSqlGenerator.cs
+++ b/src/EFCore.MySql/Migrations/MySqlMigrationsSqlGenerator.cs
@@ -7,6 +7,7 @@
using System.Globalization;
using System.Linq;
using System.Reflection;
+using System.Text;
using System.Text.RegularExpressions;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
@@ -15,12 +16,14 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations.Operations;
using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore.Update;
using Microsoft.EntityFrameworkCore.Utilities;
using Microsoft.Extensions.Logging;
using Pomelo.EntityFrameworkCore.MySql.Infrastructure.Internal;
using Pomelo.EntityFrameworkCore.MySql.Internal;
using Pomelo.EntityFrameworkCore.MySql.Metadata.Internal;
using Pomelo.EntityFrameworkCore.MySql.Storage.Internal;
+using Pomelo.EntityFrameworkCore.MySql.Update.Internal;
namespace Pomelo.EntityFrameworkCore.MySql.Migrations
{
@@ -51,17 +54,17 @@ public class MySqlMigrationsSqlGenerator : MigrationsSqlGenerator
"multipolygon",
};
- private readonly IRelationalAnnotationProvider _annotationProvider;
+ private readonly ICommandBatchPreparer _commandBatchPreparer;
private readonly IMySqlOptions _options;
private readonly RelationalTypeMapping _stringTypeMapping;
public MySqlMigrationsSqlGenerator(
[NotNull] MigrationsSqlGeneratorDependencies dependencies,
- [NotNull] IRelationalAnnotationProvider annotationProvider,
+ [NotNull] ICommandBatchPreparer commandBatchPreparer,
[NotNull] IMySqlOptions options)
: base(dependencies)
{
- _annotationProvider = annotationProvider;
+ _commandBatchPreparer = commandBatchPreparer;
_options = options;
_stringTypeMapping = dependencies.TypeMappingSource.GetMapping(typeof(string));
}
@@ -457,7 +460,7 @@ protected override void Generate(
.Append(" ON ")
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Table, operation.Schema))
.Append(" (")
- .Append(ColumnListWithIndexPrefixLength(operation, operation.Columns))
+ .Append(ColumnListWithIndexPrefixLengthAndSortOrder(operation, operation.Columns, operation[MySqlAnnotationNames.IndexPrefixLength] as int[], operation.IsDescending))
.Append(")");
IndexOptions(operation, model, builder);
@@ -504,6 +507,7 @@ protected override void Generate(
{
Check.NotNull(operation, nameof(operation));
Check.NotNull(builder, nameof(builder));
+
if (!_options.ServerVersion.Supports.Sequences)
{
throw new InvalidOperationException(
@@ -518,7 +522,7 @@ protected override void Generate(
// https://github.com/aspnet/EntityFrameworkCore/blob/master/src/EFCore.Relational/Migrations/MigrationsSqlGenerator.cs#L535-L543
var oldValue = operation.ClrType;
operation.ClrType = typeof(long);
- if (operation.StartValue <= 0 )
+ if (operation.StartValue <= 0)
{
operation.MinValue = operation.StartValue;
}
@@ -526,6 +530,55 @@ protected override void Generate(
operation.ClrType = oldValue;
}
+ protected override void Generate(AlterSequenceOperation operation, IModel model, MigrationCommandListBuilder builder)
+ {
+ Check.NotNull(operation, nameof(operation));
+ Check.NotNull(builder, nameof(builder));
+
+ if (!_options.ServerVersion.Supports.Sequences)
+ {
+ throw new InvalidOperationException(
+ $"Cannot alter sequence '{operation.Name}' because sequences are not supported in server version {_options.ServerVersion}.");
+ }
+
+ base.Generate(operation, model, builder);
+ }
+
+ protected override void Generate(DropSequenceOperation operation, IModel model, MigrationCommandListBuilder builder)
+ {
+ Check.NotNull(operation, nameof(operation));
+ Check.NotNull(builder, nameof(builder));
+
+ if (!_options.ServerVersion.Supports.Sequences)
+ {
+ throw new InvalidOperationException(
+ $"Cannot alter sequence '{operation.Name}' because sequences are not supported in server version {_options.ServerVersion}.");
+ }
+
+ base.Generate(operation, model, builder);
+ }
+
+ protected override void Generate(RenameSequenceOperation operation, IModel model, MigrationCommandListBuilder builder)
+ {
+ Check.NotNull(operation, nameof(operation));
+ Check.NotNull(builder, nameof(builder));
+
+ if (!_options.ServerVersion.Supports.Sequences)
+ {
+ throw new InvalidOperationException(
+ $"Cannot alter sequence '{operation.Name}' because sequences are not supported in server version {_options.ServerVersion}.");
+ }
+
+ builder
+ .Append("ALTER TABLE ")
+ .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Name, operation.Schema))
+ .Append(" RENAME ")
+ .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.NewName, operation.NewSchema))
+ .AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator);
+
+ EndStatement(builder);
+ }
+
///
/// Builds commands for the given
/// by making calls on the given .
@@ -1333,7 +1386,7 @@ protected override void PrimaryKeyConstraint(
IndexTraits(operation, model, builder);
builder.Append("(")
- .Append(ColumnListWithIndexPrefixLength(operation, operation.Columns))
+ .Append(ColumnListWithIndexPrefixLengthAndSortOrder(operation, operation.Columns, operation[MySqlAnnotationNames.IndexPrefixLength] as int[]))
.Append(")");
}
@@ -1359,7 +1412,7 @@ protected override void UniqueConstraint(
IndexTraits(operation, model, builder);
builder.Append("(")
- .Append(ColumnListWithIndexPrefixLength(operation, operation.Columns))
+ .Append(ColumnListWithIndexPrefixLengthAndSortOrder(operation, operation.Columns, operation[MySqlAnnotationNames.IndexPrefixLength] as int[]))
.Append(")");
}
@@ -1529,14 +1582,38 @@ protected override void ForeignKeyAction(ReferentialAction referentialAction,
}
}
- private string ColumnListWithIndexPrefixLength(MigrationOperation operation, string[] columns)
- => operation[MySqlAnnotationNames.IndexPrefixLength] is int[] prefixValues
- ? ColumnList(
- columns,
- (c, i) => prefixValues.Length > i && prefixValues[i] > 0
- ? $"({prefixValues[i]})"
- : null)
- : ColumnList(columns);
+ ///
+ /// Use VALUES batches for INSERT commands where possible.
+ ///
+ protected override void Generate(InsertDataOperation operation, IModel model, MigrationCommandListBuilder builder, bool terminate = true)
+ {
+ var sqlBuilder = new StringBuilder();
+
+ var modificationCommands = GenerateModificationCommands(operation, model).ToList();
+ var updateSqlGenerator = (IMySqlUpdateSqlGenerator)Dependencies.UpdateSqlGenerator;
+
+ foreach (var batch in _commandBatchPreparer.CreateCommandBatches(modificationCommands, moreCommandSets: true))
+ {
+ updateSqlGenerator.AppendBulkInsertOperation(sqlBuilder, batch.ModificationCommands, commandPosition: 0, out _);
+ }
+
+ builder.Append(sqlBuilder.ToString());
+
+ if (terminate)
+ {
+ builder.EndCommand();
+ }
+ }
+
+ ///
+ /// There is no need to check for explicit index collation/descending support, because ASC and DESC modifiers are being silently
+ /// ignored in versions of MySQL and MariaDB, that do not support them.
+ ///
+ private string ColumnListWithIndexPrefixLengthAndSortOrder(MigrationOperation operation, string[] columns, int[] prefixValues, bool[] isDescending = null)
+ => ColumnList(
+ columns,
+ (c, i)
+ => $"{(prefixValues is not null && prefixValues.Length > i && prefixValues[i] > 0 ? $"({prefixValues[i]})" : null)}{(isDescending is not null && (isDescending.Length == 0 || isDescending[i]) ? " DESC" : null)}");
protected virtual string ColumnList([NotNull] string[] columns, Func columnPostfix)
=> string.Join(", ", columns.Select((c, i) => Dependencies.SqlGenerationHelper.DelimitIdentifier(c) + columnPostfix?.Invoke(c, i)));
diff --git a/src/EFCore.MySql/Properties/MySqlStrings.Designer.cs b/src/EFCore.MySql/Properties/MySqlStrings.Designer.cs
index f7c8efb6e..d0ce5bef8 100644
--- a/src/EFCore.MySql/Properties/MySqlStrings.Designer.cs
+++ b/src/EFCore.MySql/Properties/MySqlStrings.Designer.cs
@@ -153,6 +153,22 @@ public static string QueryUnableToTranslateMethodWithStringComparison([CanBeNull
GetString("QueryUnableToTranslateMethodWithStringComparison", nameof(declaringTypeName), nameof(methodName), nameof(optionName)),
declaringTypeName, methodName, optionName);
+ ///
+ /// The entity type '{entityType}' is mapped to the stored procedure '{sproc}', which is configured with result columns. MySQL stored procedures do not support result columns; use output parameters instead.
+ ///
+ public static string StoredProcedureResultColumnsNotSupported(object entityType, object sproc)
+ => string.Format(
+ GetString("StoredProcedureResultColumnsNotSupported", nameof(entityType), nameof(sproc)),
+ entityType, sproc);
+
+ ///
+ /// The entity type '{entityType}' is mapped to the stored procedure '{sproc}', which is configured with a return value. MySQL stored procedures do not support return values; use an output parameter instead.
+ ///
+ public static string StoredProcedureReturnValueNotSupported(object entityType, object sproc)
+ => string.Format(
+ GetString("StoredProcedureReturnValueNotSupported", nameof(entityType), nameof(sproc)),
+ entityType, sproc);
+
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
diff --git a/src/EFCore.MySql/Properties/MySqlStrings.resx b/src/EFCore.MySql/Properties/MySqlStrings.resx
index 81345fa64..8d2943c90 100644
--- a/src/EFCore.MySql/Properties/MySqlStrings.resx
+++ b/src/EFCore.MySql/Properties/MySqlStrings.resx
@@ -234,4 +234,10 @@
The default value '{defaultValue}' is being ignored, because the database server version {version} does not support constant default values for type '{type}' and does not support default value expressions in general.
+
+ The entity type '{entityType}' is mapped to the stored procedure '{sproc}', which is configured with result columns. MySQL stored procedures do not support result columns; use output parameters instead.
+
+
+ The entity type '{entityType}' is mapped to the stored procedure '{sproc}', which is configured with a return value. MySQL stored procedures do not support return values; use an output parameter instead.
+
diff --git a/src/EFCore.MySql/Query/ExpressionVisitors/Internal/MySqlBoolOptimizingExpressionVisitor.cs b/src/EFCore.MySql/Query/ExpressionVisitors/Internal/MySqlBoolOptimizingExpressionVisitor.cs
index 79e4f8aca..ce6c72c2e 100644
--- a/src/EFCore.MySql/Query/ExpressionVisitors/Internal/MySqlBoolOptimizingExpressionVisitor.cs
+++ b/src/EFCore.MySql/Query/ExpressionVisitors/Internal/MySqlBoolOptimizingExpressionVisitor.cs
@@ -40,6 +40,17 @@ sqlExpression.TypeMapping is MySqlBoolTypeMapping &&
return sqlExpression;
}
+ protected override Expression VisitAtTimeZone(AtTimeZoneExpression atTimeZoneExpression)
+ {
+ var parentOptimize = _optimize;
+ _optimize = false;
+ var operand = (SqlExpression)Visit(atTimeZoneExpression.Operand);
+ var timeZone = (SqlExpression)Visit(atTimeZoneExpression.TimeZone);
+ _optimize = parentOptimize;
+
+ return atTimeZoneExpression.Update(operand, timeZone);
+ }
+
protected override Expression VisitCase(CaseExpression caseExpression)
{
Check.NotNull(caseExpression, nameof(caseExpression));
@@ -86,6 +97,9 @@ protected override Expression VisitColumn(ColumnExpression columnExpression)
return ApplyConversion(columnExpression, condition: false);
}
+ protected override Expression VisitDelete(DeleteExpression deleteExpression)
+ => deleteExpression.Update((SelectExpression)Visit(deleteExpression.SelectExpression));
+
protected override Expression VisitDistinct(DistinctExpression distinctExpression)
{
Check.NotNull(distinctExpression, nameof(distinctExpression));
@@ -579,5 +593,38 @@ protected override Expression VisitUnion(UnionExpression unionExpression)
return unionExpression.Update(source1, source2);
}
+
+ protected override Expression VisitUpdate(UpdateExpression updateExpression)
+ {
+ var selectExpression = (SelectExpression)Visit(updateExpression.SelectExpression);
+ var parentOptimize = _optimize;
+ _optimize = false;
+ List columnValueSetters = null;
+ for (var (i, n) = (0, updateExpression.ColumnValueSetters.Count); i < n; i++)
+ {
+ var columnValueSetter = updateExpression.ColumnValueSetters[i];
+ var newValue = (SqlExpression)Visit(columnValueSetter.Value);
+ if (columnValueSetters != null)
+ {
+ columnValueSetters.Add(new ColumnValueSetter(columnValueSetter.Column, newValue));
+ }
+ else if (!ReferenceEquals(newValue, columnValueSetter.Value))
+ {
+ columnValueSetters = new List();
+ for (var j = 0; j < i; j++)
+ {
+ columnValueSetters.Add(updateExpression.ColumnValueSetters[j]);
+ }
+
+ columnValueSetters.Add(new ColumnValueSetter(columnValueSetter.Column, newValue));
+ }
+ }
+
+ _optimize = parentOptimize;
+ return updateExpression.Update(selectExpression, columnValueSetters ?? updateExpression.ColumnValueSetters);
+ }
+
+ protected override Expression VisitJsonScalar(JsonScalarExpression jsonScalarExpression)
+ => jsonScalarExpression;
}
}
diff --git a/src/EFCore.MySql/Query/ExpressionVisitors/Internal/MySqlQuerySqlGenerator.cs b/src/EFCore.MySql/Query/ExpressionVisitors/Internal/MySqlQuerySqlGenerator.cs
index 175fecc49..49a3dbdc4 100644
--- a/src/EFCore.MySql/Query/ExpressionVisitors/Internal/MySqlQuerySqlGenerator.cs
+++ b/src/EFCore.MySql/Query/ExpressionVisitors/Internal/MySqlQuerySqlGenerator.cs
@@ -7,6 +7,7 @@
using System.Linq.Expressions;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Storage;
@@ -45,6 +46,8 @@ public class MySqlQuerySqlGenerator : QuerySqlGenerator
private const ulong LimitUpperBound = 18446744073709551610;
private readonly IMySqlOptions _options;
+ private string _removeTableAliasOld;
+ private string _removeTableAliasNew;
///
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
@@ -153,6 +156,44 @@ protected virtual Expression VisitJsonPathTraversal(MySqlJsonTraversalExpression
return expression;
}
+ protected override Expression VisitColumn(ColumnExpression columnExpression)
+ {
+ if (_removeTableAliasOld is not null &&
+ columnExpression.TableAlias == _removeTableAliasOld)
+ {
+ if (_removeTableAliasNew is not null)
+ {
+ Sql.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(_removeTableAliasNew))
+ .Append(".");
+ }
+
+ Sql.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(columnExpression.Name));
+
+ return columnExpression;
+ }
+
+ return base.VisitColumn(columnExpression);
+ }
+
+ protected override Expression VisitTable(TableExpression tableExpression)
+ {
+ if (_removeTableAliasOld is not null &&
+ tableExpression.Alias == _removeTableAliasOld)
+ {
+ if (_removeTableAliasNew is not null)
+ {
+ Sql.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(_removeTableAliasNew))
+ .Append(AliasSeparator);
+ }
+
+ Sql.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(tableExpression.Name));
+
+ return tableExpression;
+ }
+
+ return base.VisitTable(tableExpression);
+ }
+
///
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
@@ -281,6 +322,120 @@ sqlBinaryExpression.Right is SqlUnaryExpression sqlUnaryExpression &&
return sqlBinaryExpression;
}
+ protected override Expression VisitDelete(DeleteExpression deleteExpression)
+ {
+ var selectExpression = deleteExpression.SelectExpression;
+
+ if (selectExpression.Offset == null
+ && selectExpression.Having == null
+ && selectExpression.GroupBy.Count == 0
+ && selectExpression.Projection.Count == 0
+ && (selectExpression.Tables.Count == 1 || selectExpression.Orderings.Count == 0 && selectExpression.Limit is null))
+ {
+ var removeSingleTableAlias = selectExpression.Tables.Count == 1 &&
+ selectExpression.Orderings.Count > 0 || selectExpression.Limit is not null;
+
+ Sql.Append($"DELETE");
+
+ if (!removeSingleTableAlias)
+ {
+ Sql.Append($" {Dependencies.SqlGenerationHelper.DelimitIdentifier(deleteExpression.Table.Alias)}");
+ }
+
+ Sql.AppendLine().Append("FROM ");
+
+ if (removeSingleTableAlias)
+ {
+ _removeTableAliasOld = selectExpression.Tables[0].Alias;
+ _removeTableAliasNew = null;
+ }
+
+ GenerateList(selectExpression.Tables, e => Visit(e), sql => sql.AppendLine());
+
+ if (selectExpression.Predicate != null)
+ {
+ Sql.AppendLine().Append("WHERE ");
+
+ Visit(selectExpression.Predicate);
+ }
+
+ GenerateOrderings(selectExpression);
+ GenerateLimitOffset(selectExpression);
+
+ if (removeSingleTableAlias)
+ {
+ _removeTableAliasOld = null;
+ }
+
+ return deleteExpression;
+ }
+
+ throw new InvalidOperationException(
+ RelationalStrings.ExecuteOperationWithUnsupportedOperatorInSqlGeneration(nameof(RelationalQueryableExtensions.ExecuteDelete)));
+ }
+
+ protected override Expression VisitUpdate(UpdateExpression updateExpression)
+ {
+ var selectExpression = updateExpression.SelectExpression;
+
+ if (selectExpression.Offset == null
+ && selectExpression.Having == null
+ && selectExpression.Orderings.Count == 0
+ && selectExpression.GroupBy.Count == 0
+ && selectExpression.Projection.Count == 0)
+ {
+ Sql.Append("UPDATE ");
+ GenerateList(selectExpression.Tables, e => Visit(e), sql => sql.AppendLine());
+
+ Sql.AppendLine().Append("SET ");
+ Visit(updateExpression.ColumnValueSetters[0].Column);
+ Sql.Append(" = ");
+ Visit(updateExpression.ColumnValueSetters[0].Value);
+
+ using (Sql.Indent())
+ {
+ foreach (var columnValueSetter in updateExpression.ColumnValueSetters.Skip(1))
+ {
+ Sql.AppendLine(",");
+ Visit(columnValueSetter.Column);
+ Sql.Append(" = ");
+ Visit(columnValueSetter.Value);
+ }
+ }
+
+ if (selectExpression.Predicate != null)
+ {
+ Sql.AppendLine().Append("WHERE ");
+ Visit(selectExpression.Predicate);
+ }
+
+ GenerateLimitOffset(selectExpression);
+
+ return updateExpression;
+ }
+
+ throw new InvalidOperationException(
+ RelationalStrings.ExecuteOperationWithUnsupportedOperatorInSqlGeneration(nameof(RelationalQueryableExtensions.ExecuteUpdate)));
+ }
+
+ protected virtual void GenerateList(
+ IReadOnlyList items,
+ Action generationAction,
+ Action joinAction = null)
+ {
+ joinAction ??= (isb => isb.Append(", "));
+
+ for (var i = 0; i < items.Count; i++)
+ {
+ if (i > 0)
+ {
+ joinAction(Sql);
+ }
+
+ generationAction(items[i]);
+ }
+ }
+
private static bool RequiresBrackets(SqlExpression expression)
=> expression is SqlBinaryExpression
|| expression is LikeExpression
diff --git a/src/EFCore.MySql/Query/Internal/MySqlParameterBasedSqlProcessor.cs b/src/EFCore.MySql/Query/Internal/MySqlParameterBasedSqlProcessor.cs
index afcae8e88..4c2235d48 100644
--- a/src/EFCore.MySql/Query/Internal/MySqlParameterBasedSqlProcessor.cs
+++ b/src/EFCore.MySql/Query/Internal/MySqlParameterBasedSqlProcessor.cs
@@ -4,9 +4,8 @@
#nullable enable
using System.Collections.Generic;
-using JetBrains.Annotations;
+using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Query;
-using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Utilities;
using Pomelo.EntityFrameworkCore.MySql.Infrastructure.Internal;
using Pomelo.EntityFrameworkCore.MySql.Query.ExpressionVisitors.Internal;
@@ -19,7 +18,7 @@ public class MySqlParameterBasedSqlProcessor : RelationalParameterBasedSqlProces
private readonly MySqlSqlExpressionFactory _sqlExpressionFactory;
public MySqlParameterBasedSqlProcessor(
- [NotNull] RelationalParameterBasedSqlProcessorDependencies dependencies,
+ RelationalParameterBasedSqlProcessorDependencies dependencies,
bool useRelationalNulls,
IMySqlOptions options)
: base(dependencies, useRelationalNulls)
@@ -28,44 +27,48 @@ public MySqlParameterBasedSqlProcessor(
_options = options;
}
- public override SelectExpression Optimize(SelectExpression selectExpression, IReadOnlyDictionary parametersValues, out bool canCache)
+ public override Expression Optimize(
+ Expression queryExpression,
+ IReadOnlyDictionary parametersValues,
+ out bool canCache)
{
- Check.NotNull(selectExpression, nameof(selectExpression));
- Check.NotNull(parametersValues, nameof(parametersValues));
-
- selectExpression = base.Optimize(selectExpression, parametersValues, out canCache);
+ queryExpression = base.Optimize(queryExpression, parametersValues, out canCache);
if (_options.ServerVersion.Supports.MySqlBugLimit0Offset0ExistsWorkaround)
{
- selectExpression = new SkipTakeCollapsingExpressionVisitor(Dependencies.SqlExpressionFactory)
- .Process(selectExpression, parametersValues, out var canCache2);
+ queryExpression = new SkipTakeCollapsingExpressionVisitor(Dependencies.SqlExpressionFactory)
+ .Process(queryExpression, parametersValues, out var canCache2);
canCache &= canCache2;
}
if (_options.IndexOptimizedBooleanColumns)
{
- selectExpression = (SelectExpression)new MySqlBoolOptimizingExpressionVisitor(Dependencies.SqlExpressionFactory).Visit(selectExpression);
+ queryExpression = new MySqlBoolOptimizingExpressionVisitor(Dependencies.SqlExpressionFactory)
+ .Visit(queryExpression);
}
- selectExpression = (SelectExpression)new MySqlHavingExpressionVisitor(_sqlExpressionFactory).Visit(selectExpression);
+ queryExpression = new MySqlHavingExpressionVisitor(_sqlExpressionFactory).Visit(queryExpression);
// Run the compatibility checks as late in the query pipeline (before the actual SQL translation happens) as reasonable.
- selectExpression = (SelectExpression)new MySqlCompatibilityExpressionVisitor(_options).Visit(selectExpression);
+ queryExpression = new MySqlCompatibilityExpressionVisitor(_options).Visit(queryExpression);
- return selectExpression;
+ return queryExpression;
}
///
- protected override SelectExpression ProcessSqlNullability(
- SelectExpression selectExpression, IReadOnlyDictionary parametersValues, out bool canCache)
+ protected override Expression ProcessSqlNullability(
+ Expression queryExpression,
+ IReadOnlyDictionary parametersValues,
+ out bool canCache)
{
- Check.NotNull(selectExpression, nameof(selectExpression));
+ Check.NotNull(queryExpression, nameof(queryExpression));
Check.NotNull(parametersValues, nameof(parametersValues));
- selectExpression = new MySqlSqlNullabilityProcessor(Dependencies, UseRelationalNulls).Process(selectExpression, parametersValues, out canCache);
+ queryExpression = new MySqlSqlNullabilityProcessor(Dependencies, UseRelationalNulls)
+ .Process(queryExpression, parametersValues, out canCache);
- return selectExpression;
+ return queryExpression;
}
}
}
diff --git a/src/EFCore.MySql/Query/Internal/MySqlQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.MySql/Query/Internal/MySqlQueryableMethodTranslatingExpressionVisitor.cs
new file mode 100644
index 000000000..261c69ec5
--- /dev/null
+++ b/src/EFCore.MySql/Query/Internal/MySqlQueryableMethodTranslatingExpressionVisitor.cs
@@ -0,0 +1,99 @@
+// Copyright (c) Pomelo Foundation. All rights reserved.
+// Licensed under the MIT. See LICENSE in the project root for license information.
+
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Microsoft.EntityFrameworkCore.Query;
+using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
+
+namespace Pomelo.EntityFrameworkCore.MySql.Query.Internal;
+
+public class MySqlQueryableMethodTranslatingExpressionVisitor : RelationalQueryableMethodTranslatingExpressionVisitor
+{
+ public MySqlQueryableMethodTranslatingExpressionVisitor(
+ QueryableMethodTranslatingExpressionVisitorDependencies dependencies,
+ RelationalQueryableMethodTranslatingExpressionVisitorDependencies relationalDependencies,
+ QueryCompilationContext queryCompilationContext)
+ : base(dependencies, relationalDependencies, queryCompilationContext)
+ {
+ }
+
+ protected override bool IsValidSelectExpressionForExecuteDelete(
+ SelectExpression selectExpression,
+ EntityShaperExpression entityShaperExpression,
+ [NotNullWhen(true)] out TableExpression tableExpression)
+ {
+ if (selectExpression.Offset == null
+ && (!selectExpression.IsDistinct || entityShaperExpression.EntityType.FindPrimaryKey() != null)
+ && selectExpression.GroupBy.Count == 0
+ && selectExpression.Having == null
+ && (selectExpression.Tables.Count == 1 || selectExpression.Orderings.Count == 0))
+ {
+ TableExpressionBase table;
+ if (selectExpression.Tables.Count == 1)
+ {
+ table = selectExpression.Tables[0];
+ }
+ else
+ {
+ var projectionBindingExpression = (ProjectionBindingExpression)entityShaperExpression.ValueBufferExpression;
+ var entityProjectionExpression = (EntityProjectionExpression)selectExpression.GetProjection(projectionBindingExpression);
+ var column = entityProjectionExpression.BindProperty(entityShaperExpression.EntityType.GetProperties().First());
+ table = column.Table;
+ if (table is JoinExpressionBase joinExpressionBase)
+ {
+ table = joinExpressionBase.Table;
+ }
+ }
+
+ if (table is TableExpression te)
+ {
+ tableExpression = te;
+ return true;
+ }
+ }
+
+ tableExpression = null;
+ return false;
+ }
+
+ protected override bool IsValidSelectExpressionForExecuteUpdate(
+ SelectExpression selectExpression,
+ EntityShaperExpression entityShaperExpression,
+ [NotNullWhen(true)] out TableExpression tableExpression)
+ {
+ if (selectExpression.Offset == null
+ // If entity type has primary key then Distinct is no-op
+ && (!selectExpression.IsDistinct || entityShaperExpression.EntityType.FindPrimaryKey() != null)
+ && selectExpression.GroupBy.Count == 0
+ && selectExpression.Having == null
+ && selectExpression.Orderings.Count == 0)
+ {
+ TableExpressionBase table;
+ if (selectExpression.Tables.Count == 1)
+ {
+ table = selectExpression.Tables[0];
+ }
+ else
+ {
+ var projectionBindingExpression = (ProjectionBindingExpression)entityShaperExpression.ValueBufferExpression;
+ var entityProjectionExpression = (EntityProjectionExpression)selectExpression.GetProjection(projectionBindingExpression);
+ var column = entityProjectionExpression.BindProperty(entityShaperExpression.EntityType.GetProperties().First());
+ table = column.Table;
+ if (table is JoinExpressionBase joinExpressionBase)
+ {
+ table = joinExpressionBase.Table;
+ }
+ }
+
+ if (table is TableExpression te)
+ {
+ tableExpression = te;
+ return true;
+ }
+ }
+
+ tableExpression = null;
+ return false;
+ }
+}
diff --git a/src/EFCore.MySql/Query/Internal/MySqlQueryableMethodTranslatingExpressionVisitorFactory.cs b/src/EFCore.MySql/Query/Internal/MySqlQueryableMethodTranslatingExpressionVisitorFactory.cs
new file mode 100644
index 000000000..498e7a33e
--- /dev/null
+++ b/src/EFCore.MySql/Query/Internal/MySqlQueryableMethodTranslatingExpressionVisitorFactory.cs
@@ -0,0 +1,42 @@
+// Copyright (c) Pomelo Foundation. All rights reserved.
+// Licensed under the MIT. See LICENSE in the project root for license information.
+
+using Microsoft.EntityFrameworkCore.Query;
+
+namespace Pomelo.EntityFrameworkCore.MySql.Query.Internal;
+
+public class MySqlQueryableMethodTranslatingExpressionVisitorFactory : IQueryableMethodTranslatingExpressionVisitorFactory
+{
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public MySqlQueryableMethodTranslatingExpressionVisitorFactory(
+ QueryableMethodTranslatingExpressionVisitorDependencies dependencies,
+ RelationalQueryableMethodTranslatingExpressionVisitorDependencies relationalDependencies)
+ {
+ Dependencies = dependencies;
+ RelationalDependencies = relationalDependencies;
+ }
+
+ ///
+ /// Dependencies for this service.
+ ///
+ protected virtual QueryableMethodTranslatingExpressionVisitorDependencies Dependencies { get; }
+
+ ///
+ /// Relational provider-specific dependencies for this service.
+ ///
+ protected virtual RelationalQueryableMethodTranslatingExpressionVisitorDependencies RelationalDependencies { get; }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual QueryableMethodTranslatingExpressionVisitor Create(QueryCompilationContext queryCompilationContext)
+ => new MySqlQueryableMethodTranslatingExpressionVisitor(Dependencies, RelationalDependencies, queryCompilationContext);
+}
diff --git a/src/EFCore.MySql/Query/Internal/SkipTakeCollapsingExpressionVisitor.cs b/src/EFCore.MySql/Query/Internal/SkipTakeCollapsingExpressionVisitor.cs
index 69f532aca..217c83d53 100644
--- a/src/EFCore.MySql/Query/Internal/SkipTakeCollapsingExpressionVisitor.cs
+++ b/src/EFCore.MySql/Query/Internal/SkipTakeCollapsingExpressionVisitor.cs
@@ -26,8 +26,8 @@ public SkipTakeCollapsingExpressionVisitor(ISqlExpressionFactory sqlExpressionFa
_parameterValues = null!;
}
- public virtual SelectExpression Process(
- SelectExpression selectExpression,
+ public virtual Expression Process(
+ Expression selectExpression,
IReadOnlyDictionary parametersValues,
out bool canCache)
{
@@ -37,7 +37,7 @@ public virtual SelectExpression Process(
_parameterValues = parametersValues;
_canCache = true;
- var result = (SelectExpression)Visit(selectExpression);
+ var result = Visit(selectExpression);
canCache = _canCache;
diff --git a/src/EFCore.MySql/Scaffolding/Internal/MySqlDatabaseModelFactory.cs b/src/EFCore.MySql/Scaffolding/Internal/MySqlDatabaseModelFactory.cs
index eb4e3dd53..dbcfe7883 100644
--- a/src/EFCore.MySql/Scaffolding/Internal/MySqlDatabaseModelFactory.cs
+++ b/src/EFCore.MySql/Scaffolding/Internal/MySqlDatabaseModelFactory.cs
@@ -11,6 +11,7 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Scaffolding;
using Microsoft.EntityFrameworkCore.Scaffolding.Metadata;
@@ -168,6 +169,15 @@ protected virtual DatabaseModel GetDatabase(DbConnection connection, DatabaseMod
databaseModel.Tables.Add(table);
}
+ if (_options.ServerVersion.Supports.Sequences)
+ {
+ foreach (var sequence in GetSequences(connection))
+ {
+ sequence.Database = databaseModel;
+ databaseModel.Sequences.Add(sequence);
+ }
+ }
+
return databaseModel;
}
@@ -179,6 +189,16 @@ protected virtual Func GenerateTableFilter(
IReadOnlyList schemas)
=> tables.Count > 0 ? (s, t) => tables.Contains(t) : (Func)null;
+ private static Func GenerateSchemaFilter(IReadOnlyList schemas)
+ => schemas.Any()
+ ? s => $"{s} IN ({string.Join(", ", schemas.Select(EscapeLiteral))})"
+ : null;
+
+ ///
+ /// Wraps a string literal in single quotes.
+ ///
+ private static string EscapeLiteral(string s) => $"'{s}'";
+
private const string GetTablesQuery = @"SELECT
`t`.`TABLE_NAME`,
`t`.`TABLE_TYPE`,
@@ -251,7 +271,66 @@ protected virtual IEnumerable GetTables(
}
}
- private const string GetColumnsQuery = @"SELECT
+ ///
+ /// Queries the database for defined sequences and registers them with the model.
+ ///
+ private static IEnumerable GetSequences(DbConnection connection)
+ {
+ var commandText = @"SELECT
+ `t`.`TABLE_NAME`
+FROM
+ `INFORMATION_SCHEMA`.`TABLES` as `t`
+WHERE
+ `TABLE_SCHEMA` = SCHEMA()
+AND
+ `TABLE_TYPE` = 'SEQUENCE'";
+
+ var sequences = new List();
+
+ using var command = connection.CreateCommand();
+ command.CommandText = commandText;
+
+ using (var reader = command.ExecuteReader())
+ {
+ while (reader.Read())
+ {
+ var name = reader.GetValueOrDefault("TABLE_NAME");
+
+ var sequence = new DatabaseSequence
+ {
+ Schema = null,
+ Name = name,
+ };
+
+ sequences.Add(sequence);
+ }
+ }
+
+ foreach (var sequence in sequences)
+ {
+ command.CommandText = $"SELECT `START_VALUE`, `MINIMUM_VALUE`, `MAXIMUM_VALUE`, `INCREMENT`, `CYCLE_OPTION` FROM `{sequence.Name}`";
+
+ using var reader = command.ExecuteReader();
+ while (reader.Read())
+ {
+ var startValue = reader.GetValueOrDefault("START_VALUE");
+ var minimumValue = reader.GetValueOrDefault("MINIMUM_VALUE");
+ var maximumValue = reader.GetValueOrDefault("MAXIMUM_VALUE");
+ var increment = reader.GetValueOrDefault("INCREMENT");
+ var cycle = reader.GetValueOrDefault("CYCLE_OPTION");
+
+ sequence.StartValue = startValue;
+ sequence.MinValue = minimumValue;
+ sequence.MaxValue = maximumValue;
+ sequence.IncrementBy = increment;
+ sequence.IsCyclic = cycle;
+ }
+ }
+
+ return sequences;
+ }
+
+ private const string GetColumnsQuery = @"SELECT
`COLUMN_NAME`,
`ORDINAL_POSITION`,
`COLUMN_DEFAULT`,
@@ -635,6 +714,7 @@ protected virtual void GetPrimaryKeys(
`NON_UNIQUE`,
GROUP_CONCAT(`COLUMN_NAME` ORDER BY `SEQ_IN_INDEX` SEPARATOR ',') AS `COLUMNS`,
GROUP_CONCAT(CAST(IFNULL(`SUB_PART`, 0) AS CHAR) ORDER BY `SEQ_IN_INDEX` SEPARATOR ',') AS `SUB_PARTS`,
+ GROUP_CONCAT(IFNULL(`COLLATION`, 'A') ORDER BY `SEQ_IN_INDEX` SEPARATOR ',') AS `COLLATION`,
`INDEX_TYPE`
FROM `INFORMATION_SCHEMA`.`STATISTICS`
WHERE `TABLE_SCHEMA` = '{0}'
@@ -724,6 +804,11 @@ protected virtual void GetIndexes(
index[MySqlAnnotationNames.IndexPrefixLength] = null;
}
+ index.IsDescending = reader.GetValueOrDefault("COLLATION")
+ .Split(',')
+ .Select(c => c == "D")
+ .ToArray();
+
var indexType = reader.GetValueOrDefault("INDEX_TYPE");
if (string.Equals(indexType, "spatial", StringComparison.OrdinalIgnoreCase))
diff --git a/src/EFCore.MySql/Storage/Internal/MySqlRelationalConnection.cs b/src/EFCore.MySql/Storage/Internal/MySqlRelationalConnection.cs
index 1b5593180..12c5dc592 100644
--- a/src/EFCore.MySql/Storage/Internal/MySqlRelationalConnection.cs
+++ b/src/EFCore.MySql/Storage/Internal/MySqlRelationalConnection.cs
@@ -72,7 +72,7 @@ public virtual IMySqlRelationalConnection CreateMasterConnection()
set => base.DbConnection = value;
}
- private MySqlConnectionStringBuilder AddConnectionStringOptions(MySqlConnectionStringBuilder builder)
+ protected virtual MySqlConnectionStringBuilder AddConnectionStringOptions(MySqlConnectionStringBuilder builder)
{
if (CommandTimeout != null)
{
diff --git a/src/EFCore.MySql/Update/Internal/IMySqlUpdateSqlGenerator.cs b/src/EFCore.MySql/Update/Internal/IMySqlUpdateSqlGenerator.cs
index fdb5f69a6..b91daa822 100644
--- a/src/EFCore.MySql/Update/Internal/IMySqlUpdateSqlGenerator.cs
+++ b/src/EFCore.MySql/Update/Internal/IMySqlUpdateSqlGenerator.cs
@@ -13,6 +13,7 @@ public interface IMySqlUpdateSqlGenerator : IUpdateSqlGenerator
ResultSetMapping AppendBulkInsertOperation(
[NotNull] StringBuilder commandStringBuilder,
[NotNull] IReadOnlyList modificationCommands,
- int commandPosition);
+ int commandPosition,
+ out bool requiresTransaction);
}
}
diff --git a/src/EFCore.MySql/Update/Internal/MySqlModificationCommand.cs b/src/EFCore.MySql/Update/Internal/MySqlModificationCommand.cs
new file mode 100644
index 000000000..8c138c71b
--- /dev/null
+++ b/src/EFCore.MySql/Update/Internal/MySqlModificationCommand.cs
@@ -0,0 +1,68 @@
+// Copyright (c) Pomelo Foundation. All rights reserved.
+// Licensed under the MIT. See LICENSE in the project root for license information.
+
+using System;
+using System.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore.Update;
+
+namespace Pomelo.EntityFrameworkCore.MySql.Update.Internal;
+
+public class MySqlModificationCommand : ModificationCommand
+{
+ private readonly bool _detailedErrorsEnabled;
+
+ public MySqlModificationCommand(in ModificationCommandParameters modificationCommandParameters)
+ : base(in modificationCommandParameters)
+ => _detailedErrorsEnabled = modificationCommandParameters.DetailedErrorsEnabled;
+
+ public MySqlModificationCommand(in NonTrackedModificationCommandParameters modificationCommandParameters)
+ : base(in modificationCommandParameters)
+ {
+ }
+
+ public override void PropagateResults(RelationalDataReader relationalReader)
+ {
+ // The default implementation of PropagateResults skips (output) parameters, since for e.g. SQL Server these aren't yet populated
+ // when consuming the result set (propagating output columns is done later, after the reader is closed).
+ // However, in MySQL, output parameters actually get returned as the result set, so we override and take care of that here.
+ var columnCount = ColumnModifications.Count;
+
+ var readerIndex = -1;
+
+ for (var columnIndex = 0; columnIndex < columnCount; columnIndex++)
+ {
+ var columnModification = ColumnModifications[columnIndex];
+
+ switch (columnModification.Column)
+ {
+ case IColumn when columnModification.IsRead:
+ case IStoreStoredProcedureParameter { Direction: ParameterDirection.Output or ParameterDirection.InputOutput }:
+ readerIndex++;
+ break;
+
+ case IColumn:
+ case IStoreStoredProcedureParameter:
+ case null when columnModification.JsonPath is not null:
+ continue;
+
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+
+ // For regular result sets, results are always propagated back into entity properties.
+ // But with stored procedures, there may be a rows affected result column (generated by an output parameter definition).
+ // Skip these.
+ if (columnModification.Property is null ||
+ !columnModification.IsRead)
+ {
+ continue;
+ }
+
+ columnModification.Value =
+ columnModification.Property.GetReaderFieldValue(relationalReader, readerIndex, _detailedErrorsEnabled);
+ }
+ }
+}
diff --git a/src/EFCore.MySql/Update/Internal/MySqlModificationCommandBatch.cs b/src/EFCore.MySql/Update/Internal/MySqlModificationCommandBatch.cs
index bda59c080..b191378d7 100644
--- a/src/EFCore.MySql/Update/Internal/MySqlModificationCommandBatch.cs
+++ b/src/EFCore.MySql/Update/Internal/MySqlModificationCommandBatch.cs
@@ -1,202 +1,396 @@
-// Copyright (c) Pomelo Foundation. All rights reserved.
+// Copyright (c) Pomelo Foundation. All rights reserved.
// Licensed under the MIT. See LICENSE in the project root for license information.
using System;
using System.Collections.Generic;
+using System.Data;
+using System.Diagnostics;
using System.Linq;
-using System.Text;
-using JetBrains.Annotations;
+using System.Threading;
+using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
-using Microsoft.EntityFrameworkCore.Internal;
-using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Update;
+using Microsoft.EntityFrameworkCore.Utilities;
+using Microsoft.EntityFrameworkCore.Storage;
-namespace Pomelo.EntityFrameworkCore.MySql.Update.Internal
+namespace Pomelo.EntityFrameworkCore.MySql.Update.Internal;
+
+///
+/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+/// the same compatibility standards as public APIs. It may be changed or removed without notice in
+/// any release. You should only use it directly in your code with extreme caution and knowing that
+/// doing so can result in application failures when updating to a new Entity Framework Core release.
+///
+public class MySqlModificationCommandBatch : AffectedCountModificationCommandBatch
{
- ///
- /// This API supports the Entity Framework Core infrastructure and is not intended to be used
- /// directly from your code. This API may change or be removed in future releases.
- ///
- public class MySqlModificationCommandBatch : AffectedCountModificationCommandBatch
+ private readonly List _pendingBulkInsertCommands = new();
+
+ public MySqlModificationCommandBatch(
+ ModificationCommandBatchFactoryDependencies dependencies,
+ int maxBatchSize)
+ : base(dependencies, maxBatchSize)
{
- private const int DefaultNetworkPacketSizeBytes = 4096;
- private const int MaxScriptLength = 65536 * DefaultNetworkPacketSizeBytes / 2;
- private const int MaxParameterCount = 2100;
- private const int MaxRowCount = 1000;
- private int _parameterCount = 1; // Implicit parameter for the command text
- private readonly int _maxBatchSize;
- private readonly List _bulkInsertCommands = new List();
- private int _commandsLeftToLengthCheck = 50;
-
- ///
- /// This API supports the Entity Framework Core infrastructure and is not intended to be used
- /// directly from your code. This API may change or be removed in future releases.
- ///
- public MySqlModificationCommandBatch(
- [NotNull] ModificationCommandBatchFactoryDependencies dependencies,
- int? maxBatchSize)
- : base(dependencies)
- {
- if (maxBatchSize is <= 0)
- {
- throw new ArgumentOutOfRangeException(nameof(maxBatchSize), RelationalStrings.InvalidMaxBatchSize(maxBatchSize.Value));
- }
+ }
+
+ protected new virtual IMySqlUpdateSqlGenerator UpdateSqlGenerator
+ => (IMySqlUpdateSqlGenerator)base.UpdateSqlGenerator;
- _maxBatchSize = Math.Min(maxBatchSize ?? int.MaxValue, MaxRowCount);
+ protected override void RollbackLastCommand(IReadOnlyModificationCommand modificationCommand)
+ {
+ if (_pendingBulkInsertCommands.Count > 0)
+ {
+ _pendingBulkInsertCommands.RemoveAt(_pendingBulkInsertCommands.Count - 1);
}
- ///
- /// This API supports the Entity Framework Core infrastructure and is not intended to be used
- /// directly from your code. This API may change or be removed in future releases.
- ///
- protected new virtual IMySqlUpdateSqlGenerator UpdateSqlGenerator => (IMySqlUpdateSqlGenerator)base.UpdateSqlGenerator;
+ //////
+ // Pulled up from the base implementation to support our _pendingParameters field:
+
+ for (var i = 0; i < _pendingParameters; i++)
+ {
+ var parameterIndex = RelationalCommandBuilder.Parameters.Count - 1;
+ var parameter = RelationalCommandBuilder.Parameters[parameterIndex];
+
+ RelationalCommandBuilder.RemoveParameterAt(parameterIndex);
+ ParameterValues.Remove(parameter.InvariantName);
+ }
+
+ //
+ //////
+
+ base.RollbackLastCommand(modificationCommand);
+ }
+
+ private void ApplyPendingBulkInsertCommands()
+ {
+ if (_pendingBulkInsertCommands.Count == 0)
+ {
+ return;
+ }
+
+ var commandPosition = ResultSetMappings.Count;
+
+ var wasCachedCommandTextEmpty = IsCommandTextEmpty;
+
+ var resultSetMapping = UpdateSqlGenerator.AppendBulkInsertOperation(
+ SqlBuilder, _pendingBulkInsertCommands, commandPosition, out var requiresTransaction);
+
+ SetRequiresTransaction(!wasCachedCommandTextEmpty || requiresTransaction);
+
+ for (var i = 0; i < _pendingBulkInsertCommands.Count; i++)
+ {
+ ResultSetMappings.Add(resultSetMapping);
+ }
- ///
- /// This API supports the Entity Framework Core infrastructure and is not intended to be used
- /// directly from your code. This API may change or be removed in future releases.
- ///
+ if (resultSetMapping != ResultSetMapping.NoResults)
+ {
+ ResultSetMappings[^1] = ResultSetMapping.LastInResultSet;
+ }
+ }
- protected override bool CanAddCommand(IReadOnlyModificationCommand modificationCommand)
+ protected override void AddCommand(IReadOnlyModificationCommand modificationCommand)
+ {
+ if (modificationCommand.EntityState == EntityState.Added && modificationCommand.StoreStoredProcedure is null)
{
- if (ModificationCommands.Count >= _maxBatchSize)
+ if (_pendingBulkInsertCommands.Count > 0
+ && !CanBeInsertedInSameStatement(_pendingBulkInsertCommands[0], modificationCommand))
{
- return false;
+ // The new Add command cannot be added to the pending bulk insert commands (e.g. different table).
+ // Write out the pending commands before starting a new pending chain.
+ ApplyPendingBulkInsertCommands();
+ _pendingBulkInsertCommands.Clear();
}
- var additionalParameterCount = CountParameters(modificationCommand);
-
- if (_parameterCount + additionalParameterCount >= MaxParameterCount)
+ _pendingBulkInsertCommands.Add(modificationCommand);
+ AddParameters(modificationCommand);
+ }
+ else
+ {
+ // If we have any pending bulk insert commands, write them out before the next non-Add command
+ if (_pendingBulkInsertCommands.Count > 0)
{
- return false;
+ // Note that we don't care about the transactionality of the bulk insert SQL, since there's the additional non-Add
+ // command coming right afterwards, and so a transaction is required in any case.
+ ApplyPendingBulkInsertCommands();
+ _pendingBulkInsertCommands.Clear();
}
- _parameterCount += additionalParameterCount;
- return true;
+ base.AddCommand(modificationCommand);
}
+ }
+
+ private static bool CanBeInsertedInSameStatement(
+ IReadOnlyModificationCommand firstCommand,
+ IReadOnlyModificationCommand secondCommand)
+ => firstCommand.TableName == secondCommand.TableName
+ && firstCommand.Schema == secondCommand.Schema
+ && firstCommand.ColumnModifications.Where(o => o.IsWrite).Select(o => o.ColumnName).SequenceEqual(
+ secondCommand.ColumnModifications.Where(o => o.IsWrite).Select(o => o.ColumnName))
+ && firstCommand.ColumnModifications.Where(o => o.IsRead).Select(o => o.ColumnName).SequenceEqual(
+ secondCommand.ColumnModifications.Where(o => o.IsRead).Select(o => o.ColumnName));
+
+ public override void Complete(bool moreBatchesExpected)
+ {
+ ApplyPendingBulkInsertCommands();
- ///
- /// This API supports the Entity Framework Core infrastructure and is not intended to be used
- /// directly from your code. This API may change or be removed in future releases.
- ///
- protected override bool IsCommandTextValid()
+ base.Complete(moreBatchesExpected);
+ }
+
+ ///
+ /// Consumes the data reader created by ,
+ /// propagating values back into the .
+ ///
+ /// The ordinal of the first command being consumed.
+ /// The data reader.
+ /// The ordinal of the next result set that must be consumed.
+ protected override int ConsumeResultSet(int startCommandIndex, RelationalDataReader reader)
+ {
+ var commandIndex = startCommandIndex;
+ var rowsAffected = 0;
+ do
{
- if (--_commandsLeftToLengthCheck < 0)
+ if (!reader.Read())
{
- var commandTextLength = GetCommandText().Length;
- if (commandTextLength >= MaxScriptLength)
+ var expectedRowsAffected = rowsAffected + 1;
+ while (++commandIndex < ResultSetMappings.Count
+ && ResultSetMappings[commandIndex - 1].HasFlag(ResultSetMapping.NotLastInResultSet))
{
- return false;
+ expectedRowsAffected++;
}
- var avarageCommandLength = commandTextLength / ModificationCommands.Count;
- var expectedAdditionalCommandCapacity = (MaxScriptLength - commandTextLength) / avarageCommandLength;
- _commandsLeftToLengthCheck = Math.Max(1, expectedAdditionalCommandCapacity / 4);
+ ThrowAggregateUpdateConcurrencyException(reader, commandIndex, expectedRowsAffected, rowsAffected);
+ }
+ else
+ {
+ var resultSetMapping = ResultSetMappings[commandIndex];
+
+ var command = ModificationCommands[
+ resultSetMapping.HasFlag(ResultSetMapping.IsPositionalResultMappingEnabled)
+ ? startCommandIndex + reader.DbDataReader.GetInt32(reader.DbDataReader.FieldCount - 1)
+ : commandIndex];
+
+ Check.DebugAssert(
+ !resultSetMapping.HasFlag(ResultSetMapping.ResultSetWithRowsAffectedOnly),
+ "!resultSetMapping.HasFlag(ResultSetMapping.ResultSetWithRowsAffectedOnly)");
+
+ //////
+ // Addition to base method:
+ ConsumeRowsAffectedFromResultSet(command, reader, commandIndex);
+ //
+ //////
+
+ command.PropagateResults(reader);
}
- return true;
+ rowsAffected++;
}
+ while (++commandIndex < ResultSetMappings.Count
+ && ResultSetMappings[commandIndex - 1].HasFlag(ResultSetMapping.NotLastInResultSet));
- ///
- /// This API supports the Entity Framework Core infrastructure and is not intended to be used
- /// directly from your code. This API may change or be removed in future releases.
- ///
- protected override int GetParameterCount()
- => _parameterCount;
+ return commandIndex - 1;
+ }
- private static int CountParameters(IReadOnlyModificationCommand modificationCommand)
+ ///
+ /// Consumes the data reader created by ,
+ /// propagating values back into the .
+ ///
+ /// The ordinal of the first result set being consumed.
+ /// The data reader.
+ /// A to observe while waiting for the task to complete.
+ ///
+ /// A task that represents the asynchronous operation.
+ /// The task contains the ordinal of the next command that must be consumed.
+ ///
+ /// If the is canceled.
+ protected override async Task ConsumeResultSetAsync(int startCommandIndex, RelationalDataReader reader, CancellationToken cancellationToken)
+ {
+ var commandIndex = startCommandIndex;
+ var rowsAffected = 0;
+ do
{
- var parameterCount = 0;
- foreach (var columnModification in modificationCommand.ColumnModifications)
+ if (!await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
{
- if (columnModification.UseCurrentValueParameter)
+ var expectedRowsAffected = rowsAffected + 1;
+ while (++commandIndex < ResultSetMappings.Count
+ && ResultSetMappings[commandIndex - 1].HasFlag(ResultSetMapping.NotLastInResultSet))
{
- parameterCount++;
+ expectedRowsAffected++;
}
- if (columnModification.UseOriginalValueParameter)
- {
- parameterCount++;
- }
+ await ThrowAggregateUpdateConcurrencyExceptionAsync(
+ reader, commandIndex, expectedRowsAffected, rowsAffected, cancellationToken).ConfigureAwait(false);
}
+ else
+ {
+ var resultSetMapping = ResultSetMappings[commandIndex];
- return parameterCount;
- }
+ var command = ModificationCommands[
+ resultSetMapping.HasFlag(ResultSetMapping.IsPositionalResultMappingEnabled)
+ ? startCommandIndex + reader.DbDataReader.GetInt32(reader.DbDataReader.FieldCount - 1)
+ : commandIndex];
- ///
- /// This API supports the Entity Framework Core infrastructure and is not intended to be used
- /// directly from your code. This API may change or be removed in future releases.
- ///
- protected override void ResetCommandText()
- {
- base.ResetCommandText();
- _bulkInsertCommands.Clear();
+ Check.DebugAssert(
+ !resultSetMapping.HasFlag(ResultSetMapping.ResultSetWithRowsAffectedOnly),
+ "!resultSetMapping.HasFlag(ResultSetMapping.ResultSetWithRowsAffectedOnly)");
+
+ //////
+ // Addition to base method:
+ ConsumeRowsAffectedFromResultSet(command, reader, commandIndex);
+ //
+ //////
+
+ command.PropagateResults(reader);
+ }
+
+ rowsAffected++;
}
+ while (++commandIndex < ResultSetMappings.Count
+ && ResultSetMappings[commandIndex - 1].HasFlag(ResultSetMapping.NotLastInResultSet));
- ///
- /// This API supports the Entity Framework Core infrastructure and is not intended to be used
- /// directly from your code. This API may change or be removed in future releases.
- ///
- protected override string GetCommandText()
- => base.GetCommandText() + GetBulkInsertCommandText(ModificationCommands.Count);
+ return commandIndex - 1;
+ }
- private string GetBulkInsertCommandText(int lastIndex)
+ protected virtual void ConsumeRowsAffectedFromResultSet(
+ IReadOnlyModificationCommand command,
+ RelationalDataReader reader,
+ int commandIndex)
+ {
+ if (command.StoreStoredProcedure is not null &&
+ command.RowsAffectedColumn is { } rowsAffectedColumn)
{
- if (_bulkInsertCommands.Count == 0)
- {
- return string.Empty;
- }
+ var rowsAffectedParameter = (IStoreStoredProcedureParameter)rowsAffectedColumn;
+
+ Debug.Assert(rowsAffectedParameter.Direction == ParameterDirection.Output);
+
+ var readerIndex = -1;
- var stringBuilder = new StringBuilder();
- var resultSetMapping = UpdateSqlGenerator.AppendBulkInsertOperation(stringBuilder, _bulkInsertCommands, lastIndex - _bulkInsertCommands.Count);
- for (var i = lastIndex - _bulkInsertCommands.Count; i < lastIndex; i++)
+ for (var i = 0; i < command.ColumnModifications.Count; i++)
{
- CommandResultSet[i] = resultSetMapping;
+ var columnModification = command.ColumnModifications[i];
+ if (columnModification.Column is IStoreStoredProcedureParameter
+ {
+ Direction: ParameterDirection.Output or ParameterDirection.InputOutput
+ })
+ {
+ readerIndex++;
+ }
+
+ if (columnModification.Column == rowsAffectedColumn)
+ {
+ break;
+ }
}
- if (resultSetMapping != ResultSetMapping.NoResultSet)
+ if (reader.DbDataReader.GetInt32(readerIndex) != 1)
{
- CommandResultSet[lastIndex - 1] = ResultSetMapping.LastInResultSet;
+ ThrowAggregateUpdateConcurrencyException(reader, commandIndex + 1, 1, 0);
}
-
- return stringBuilder.ToString();
}
+ }
- ///
- /// This API supports the Entity Framework Core infrastructure and is not intended to be used
- /// directly from your code. This API may change or be removed in future releases.
- ///
- protected override void UpdateCachedCommandText(int commandPosition)
+ protected override void AddParameter(IColumnModification columnModification)
+ {
+ var direction = columnModification.Column switch
{
- var newModificationCommand = ModificationCommands[commandPosition];
+ IStoreStoredProcedureParameter storedProcedureParameter => storedProcedureParameter.Direction,
+ IStoreStoredProcedureReturnValue => ParameterDirection.Output,
+ _ => ParameterDirection.Input
+ };
+
+ //////
+ // Start of injected code.
- if (newModificationCommand.EntityState == EntityState.Added)
+ // MySQL stored procedures cannot return a regular result set, and output parameter values are simply sent back as the
+ // result set; this is very different from SQL Server, where output parameter values can be sent back in addition to result
+ // sets. So we avoid adding MySqlParameters for output parameters - we'll just retrieve and propagate the values below when
+ // consuming the result set.
+ // Because MySqlConnector throws if we use an INOUT or OUT parameter for CommandType.Text commands, we skip
+ // ParameterDirection.Output parameters entirely and change ParameterDirection.InputOutput to ParameterDirection.Input.
+ if (columnModification.Column is IStoreStoredProcedureParameter parameter)
+ {
+ if (parameter.Direction.HasFlag(ParameterDirection.Output))
{
- if (_bulkInsertCommands.Count > 0
- && !CanBeInsertedInSameStatement(_bulkInsertCommands[0], newModificationCommand))
+ if (!parameter.Direction.HasFlag(ParameterDirection.Input))
{
- CachedCommandText.Append(GetBulkInsertCommandText(commandPosition));
- _bulkInsertCommands.Clear();
+ return;
}
- _bulkInsertCommands.Add(newModificationCommand);
- LastCachedCommandIndex = commandPosition;
- }
- else
- {
- CachedCommandText.Append(GetBulkInsertCommandText(commandPosition));
- _bulkInsertCommands.Clear();
+ direction = ParameterDirection.Input;
+
+ var value = columnModification.UseCurrentValueParameter
+ ? columnModification.Value
+ : columnModification.UseOriginalValueParameter
+ ? columnModification.OriginalValue
+ : null;
- base.UpdateCachedCommandText(commandPosition);
+ if (value is null)
+ {
+ return;
+ }
}
}
- private static bool CanBeInsertedInSameStatement(IReadOnlyModificationCommand firstCommand, IReadOnlyModificationCommand secondCommand)
- => string.Equals(firstCommand.TableName, secondCommand.TableName, StringComparison.Ordinal)
- && string.Equals(firstCommand.Schema, secondCommand.Schema, StringComparison.Ordinal)
- && firstCommand.ColumnModifications.Where(o => o.IsWrite).Select(o => o.ColumnName).SequenceEqual(
- secondCommand.ColumnModifications.Where(o => o.IsWrite).Select(o => o.ColumnName))
- && firstCommand.ColumnModifications.Where(o => o.IsRead).Select(o => o.ColumnName).SequenceEqual(
- secondCommand.ColumnModifications.Where(o => o.IsRead).Select(o => o.ColumnName));
+ // End of injected code.
+ //////
+
+ // For the case where the same modification has both current and original value parameters, and corresponds to an in/out parameter,
+ // we only want to add a single parameter. This will happen below.
+ if (columnModification.UseCurrentValueParameter
+ && !(columnModification.UseOriginalValueParameter && direction == ParameterDirection.InputOutput))
+ {
+ AddParameterCore(
+ columnModification.ParameterName, columnModification.UseCurrentValue
+ ? columnModification.Value
+ : direction == ParameterDirection.InputOutput
+ ? DBNull.Value
+ : null);
+ }
+
+ if (columnModification.UseOriginalValueParameter)
+ {
+ Check.DebugAssert(direction.HasFlag(ParameterDirection.Input), "direction.HasFlag(ParameterDirection.Input)");
+
+ AddParameterCore(columnModification.OriginalParameterName, columnModification.OriginalValue);
+ }
+
+ void AddParameterCore(string name, object value)
+ {
+ RelationalCommandBuilder.AddParameter(
+ name,
+ Dependencies.SqlGenerationHelper.GenerateParameterName(name),
+ columnModification.TypeMapping!,
+ columnModification.IsNullable,
+ direction);
+
+ ParameterValues.Add(name, value);
+
+ _pendingParameters++;
+ }
}
+
+ ///
+ /// We override this method only to support our _pendingParameters field.
+ ///
+ public override bool TryAddCommand(IReadOnlyModificationCommand modificationCommand)
+ {
+ if (StoreCommand is not null)
+ {
+ throw new InvalidOperationException(RelationalStrings.ModificationCommandBatchAlreadyComplete);
+ }
+
+ if (ModificationCommands.Count >= MaxBatchSize)
+ {
+ return false;
+ }
+
+ _pendingParameters = 0;
+
+ return base.TryAddCommand(modificationCommand);
+ }
+
+ ///
+ /// We use _pendingParameters only to support our AddParameter implementation.
+ ///
+ private int _pendingParameters;
}
diff --git a/src/EFCore.MySql/Update/Internal/MySqlModificationCommandBatchFactory.cs b/src/EFCore.MySql/Update/Internal/MySqlModificationCommandBatchFactory.cs
index 7c789bcbf..50fdb7b7c 100644
--- a/src/EFCore.MySql/Update/Internal/MySqlModificationCommandBatchFactory.cs
+++ b/src/EFCore.MySql/Update/Internal/MySqlModificationCommandBatchFactory.cs
@@ -1,43 +1,54 @@
// Copyright (c) Pomelo Foundation. All rights reserved.
// Licensed under the MIT. See LICENSE in the project root for license information.
+using System;
using System.Linq;
using Pomelo.EntityFrameworkCore.MySql.Infrastructure.Internal;
-using JetBrains.Annotations;
+using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Update;
-using Microsoft.EntityFrameworkCore.Utilities;
-namespace Pomelo.EntityFrameworkCore.MySql.Update.Internal
+namespace Pomelo.EntityFrameworkCore.MySql.Update.Internal;
+
+public class MySqlModificationCommandBatchFactory : IModificationCommandBatchFactory
{
- public class MySqlModificationCommandBatchFactory : IModificationCommandBatchFactory
- {
- private readonly ModificationCommandBatchFactoryDependencies _dependencies;
- private readonly IDbContextOptions _options;
+ private const int DefaultMaxBatchSize = 42;
+ private const int MaxMaxBatchSize = 1000;
+ private readonly int _maxBatchSize;
- public MySqlModificationCommandBatchFactory(
- [NotNull] ModificationCommandBatchFactoryDependencies dependencies,
- [NotNull] IDbContextOptions options)
- {
- Check.NotNull(dependencies, nameof(dependencies));
- Check.NotNull(options, nameof(options));
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public MySqlModificationCommandBatchFactory(ModificationCommandBatchFactoryDependencies dependencies,
+ IDbContextOptions options)
+ {
+ Dependencies = dependencies;
- _dependencies = dependencies;
- _options = options;
- }
+ _maxBatchSize = Math.Min(
+ options.Extensions.OfType().FirstOrDefault()?.MaxBatchSize ?? DefaultMaxBatchSize,
+ MaxMaxBatchSize);
- ///
- /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
- /// the same compatibility standards as public APIs. It may be changed or removed without notice in
- /// any release. You should only use it directly in your code with extreme caution and knowing that
- /// doing so can result in application failures when updating to a new Entity Framework Core release.
- ///
- public virtual ModificationCommandBatch Create()
+ if (_maxBatchSize <= 0)
{
- var optionsExtension = _options.Extensions.OfType().FirstOrDefault();
-
- return new MySqlModificationCommandBatch(_dependencies, optionsExtension?.MaxBatchSize);
+ throw new ArgumentOutOfRangeException(
+ nameof(RelationalOptionsExtension.MaxBatchSize), RelationalStrings.InvalidMaxBatchSize(_maxBatchSize));
}
}
+
+ ///
+ /// Relational provider-specific dependencies for this service.
+ ///
+ protected virtual ModificationCommandBatchFactoryDependencies Dependencies { get; }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual ModificationCommandBatch Create()
+ => new MySqlModificationCommandBatch(Dependencies, _maxBatchSize);
}
diff --git a/src/EFCore.MySql/Update/Internal/MySqlModificationCommandFactory.cs b/src/EFCore.MySql/Update/Internal/MySqlModificationCommandFactory.cs
new file mode 100644
index 000000000..275b62454
--- /dev/null
+++ b/src/EFCore.MySql/Update/Internal/MySqlModificationCommandFactory.cs
@@ -0,0 +1,17 @@
+// Copyright (c) Pomelo Foundation. All rights reserved.
+// Licensed under the MIT. See LICENSE in the project root for license information.
+
+using Microsoft.EntityFrameworkCore.Update;
+
+namespace Pomelo.EntityFrameworkCore.MySql.Update.Internal;
+
+public class MySqlModificationCommandFactory : IModificationCommandFactory
+{
+ public virtual IModificationCommand CreateModificationCommand(
+ in ModificationCommandParameters modificationCommandParameters)
+ => new MySqlModificationCommand(modificationCommandParameters);
+
+ public virtual INonTrackedModificationCommand CreateNonTrackedModificationCommand(
+ in NonTrackedModificationCommandParameters modificationCommandParameters)
+ => new MySqlModificationCommand(modificationCommandParameters);
+}
diff --git a/src/EFCore.MySql/Update/Internal/MySqlUpdateSqlGenerator.cs b/src/EFCore.MySql/Update/Internal/MySqlUpdateSqlGenerator.cs
index 1c8d31b0e..20fd1ec24 100644
--- a/src/EFCore.MySql/Update/Internal/MySqlUpdateSqlGenerator.cs
+++ b/src/EFCore.MySql/Update/Internal/MySqlUpdateSqlGenerator.cs
@@ -2,7 +2,7 @@
// Licensed under the MIT. See LICENSE in the project root for license information.
using System.Collections.Generic;
-using System.Diagnostics;
+using System.Data;
using System.Globalization;
using System.Linq;
using System.Text;
@@ -11,96 +11,67 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Update;
using Microsoft.EntityFrameworkCore.Utilities;
+using Pomelo.EntityFrameworkCore.MySql.Infrastructure.Internal;
namespace Pomelo.EntityFrameworkCore.MySql.Update.Internal
{
- // TODO: Revamp
- public class MySqlUpdateSqlGenerator : UpdateSqlGenerator, IMySqlUpdateSqlGenerator
+ public class MySqlUpdateSqlGenerator : UpdateAndSelectSqlGenerator, IMySqlUpdateSqlGenerator
{
- ///
- /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
- /// the same compatibility standards as public APIs. It may be changed or removed without notice in
- /// any release. You should only use it directly in your code with extreme caution and knowing that
- /// doing so can result in application failures when updating to a new Entity Framework Core release.
- ///
+ [NotNull] private readonly IMySqlOptions _options;
+
public MySqlUpdateSqlGenerator(
- [NotNull] UpdateSqlGeneratorDependencies dependencies)
+ [NotNull] UpdateSqlGeneratorDependencies dependencies,
+ [NotNull] IMySqlOptions options)
: base(dependencies)
{
+ _options = options;
}
- ///
- /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
- /// the same compatibility standards as public APIs. It may be changed or removed without notice in
- /// any release. You should only use it directly in your code with extreme caution and knowing that
- /// doing so can result in application failures when updating to a new Entity Framework Core release.
- ///
+ public override ResultSetMapping AppendInsertOperation(
+ StringBuilder commandStringBuilder,
+ IReadOnlyModificationCommand command,
+ int commandPosition,
+ out bool requiresTransaction)
+ => _options.ServerVersion.Supports.Returning ||
+ command.ColumnModifications.All(o => !o.IsRead)
+ ? AppendInsertReturningOperation(commandStringBuilder, command, commandPosition, out requiresTransaction)
+ : base.AppendInsertOperation(commandStringBuilder, command, commandPosition, out requiresTransaction);
+
public virtual ResultSetMapping AppendBulkInsertOperation(
StringBuilder commandStringBuilder,
IReadOnlyList modificationCommands,
- int commandPosition)
+ int commandPosition,
+ out bool requiresTransaction)
{
- var table = StoreObjectIdentifier.Table(modificationCommands[0].TableName, modificationCommands[0].Schema);
-
- if (modificationCommands.Count == 1
- && modificationCommands[0].ColumnModifications.All(o =>
- !o.IsKey
- || !o.IsRead
- || o.Property?.GetValueGenerationStrategy(table) == MySqlValueGenerationStrategy.IdentityColumn))
+ if (modificationCommands.Count == 1)
{
- return AppendInsertOperation(commandStringBuilder, modificationCommands[0], commandPosition);
+ return AppendInsertOperation(commandStringBuilder, modificationCommands[0], commandPosition, out requiresTransaction);
}
var readOperations = modificationCommands[0].ColumnModifications.Where(o => o.IsRead).ToList();
var writeOperations = modificationCommands[0].ColumnModifications.Where(o => o.IsWrite).ToList();
- var keyOperations = modificationCommands[0].ColumnModifications.Where(o => o.IsKey).ToList();
-
- var nonIdentityOperations = modificationCommands[0].ColumnModifications
- .Where(o => o.Property?.GetValueGenerationStrategy(table) != MySqlValueGenerationStrategy.IdentityColumn)
- .ToList();
-
- var defaultValuesOnly = writeOperations.Count == 0;
- if (defaultValuesOnly)
- {
- if (nonIdentityOperations.Count == 0
- || readOperations.Count == 0)
- {
- foreach (var modification in modificationCommands)
- {
- AppendInsertOperation(commandStringBuilder, modification, commandPosition);
- }
-
- return readOperations.Count == 0
- ? ResultSetMapping.NoResultSet
- : ResultSetMapping.LastInResultSet;
- }
-
- if (nonIdentityOperations.Count > 1)
- {
- nonIdentityOperations = new List { nonIdentityOperations.First() };
- }
- }
if (readOperations.Count == 0)
{
- return AppendBulkInsertWithoutServerValues(commandStringBuilder, modificationCommands, writeOperations);
+ return AppendInsertMultipleRowsInSingleStatementOperation(commandStringBuilder, modificationCommands, writeOperations, out requiresTransaction);
}
+ requiresTransaction = modificationCommands.Count > 1;
foreach (var modification in modificationCommands)
{
- AppendInsertOperation(commandStringBuilder, modification, commandPosition);
+ AppendInsertOperation(commandStringBuilder, modification, commandPosition, out var localRequiresTransaction);
+ requiresTransaction = requiresTransaction || localRequiresTransaction;
}
return ResultSetMapping.LastInResultSet;
}
- private ResultSetMapping AppendBulkInsertWithoutServerValues(
+ private ResultSetMapping AppendInsertMultipleRowsInSingleStatementOperation(
StringBuilder commandStringBuilder,
IReadOnlyList modificationCommands,
- List writeOperations)
+ List writeOperations,
+ out bool requiresTransaction)
{
- Debug.Assert(writeOperations.Count > 0);
-
var name = modificationCommands[0].TableName;
var schema = modificationCommands[0].Schema;
@@ -114,7 +85,10 @@ private ResultSetMapping AppendBulkInsertWithoutServerValues(
}
commandStringBuilder.Append(SqlGenerationHelper.StatementTerminator).AppendLine();
- return ResultSetMapping.NoResultSet;
+ // A single INSERT command should run atomically, regardless of how many value lists it contains.
+ requiresTransaction = false;
+
+ return ResultSetMapping.NoResults;
}
protected override void AppendInsertCommandHeader(
@@ -162,6 +136,14 @@ protected override void AppendValues(
}
}
+ public override ResultSetMapping AppendDeleteOperation(StringBuilder commandStringBuilder,
+ IReadOnlyModificationCommand command,
+ int commandPosition,
+ out bool requiresTransaction)
+ => _options.ServerVersion.Supports.Returning
+ ? AppendDeleteReturningOperation(commandStringBuilder, command, commandPosition, out requiresTransaction)
+ : base.AppendDeleteOperation(commandStringBuilder, command, commandPosition, out requiresTransaction);
+
protected override ResultSetMapping AppendSelectAffectedCountCommand(StringBuilder commandStringBuilder, string name, string schema, int commandPosition)
{
commandStringBuilder
@@ -169,36 +151,7 @@ protected override ResultSetMapping AppendSelectAffectedCountCommand(StringBuild
.Append(SqlGenerationHelper.StatementTerminator).AppendLine()
.AppendLine();
- return ResultSetMapping.LastInResultSet;
- }
-
- protected override void AppendWhereAffectedClause(
- StringBuilder commandStringBuilder,
- IReadOnlyList operations)
- {
- Check.NotNull(commandStringBuilder, nameof(commandStringBuilder));
- Check.NotNull(operations, nameof(operations));
-
- // If a compound key consists of an auto_increment column and a database generated column (e.g. a DEFAULT
- // value), then we only want to filter by `LAST_INSERT_ID()`, because we can't know what the other generated
- // values are.
- // Therefore, we filter out the key columns that are marked as `read`, but are not an auto_increment column,
- // so that `AppendIdentityWhereCondition()` can safely called for the remaining auto_increment column.
- // Because we currently use `MySqlValueGenerationStrategy.IdentityColumn` for auto_increment columns as well
- // as CURRENT_TIMESTAMP columns, we need to use `MySqlPropertyExtensions.IsCompatibleAutoIncrementColumn()`
- // to ensure, that the column is actually an auto_increment column.
- // See https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/issues/1300
- var nonDefaultOperations = operations
- .Where(
- o => !o.IsKey ||
- !o.IsRead ||
- o.Property == null ||
- !o.Property.ValueGenerated.HasFlag(ValueGenerated.OnAdd) ||
- MySqlPropertyExtensions.IsCompatibleAutoIncrementColumn(o.Property))
- .ToList()
- .AsReadOnly();
-
- base.AppendWhereAffectedClause(commandStringBuilder, nonDefaultOperations);
+ return ResultSetMapping.LastInResultSet | ResultSetMapping.ResultSetWithRowsAffectedOnly;
}
protected override void AppendIdentityWhereCondition(StringBuilder commandStringBuilder, IColumnModification columnModification)
@@ -212,5 +165,197 @@ protected override void AppendRowsAffectedWhereCondition(StringBuilder commandSt
=> commandStringBuilder
.Append("ROW_COUNT() = ")
.Append(expectedRowsAffected.ToString(CultureInfo.InvariantCulture));
+
+ public override ResultSetMapping AppendStoredProcedureCall(
+ StringBuilder commandStringBuilder,
+ IReadOnlyModificationCommand command,
+ int commandPosition,
+ out bool requiresTransaction)
+ {
+ Check.DebugAssert(command.StoreStoredProcedure is not null, "command.StoreStoredProcedure is not null");
+
+ var storedProcedure = command.StoreStoredProcedure;
+
+ Check.DebugAssert(storedProcedure.Parameters.Any(), "Stored procedure call without parameters");
+
+ var resultSetMapping = ResultSetMapping.NoResults;
+
+ // IN parameters will get injected directly into the argument list of the CALL statement.
+ // FOR INOUT or OUT parameters, we will declare variables.
+ // For OUT parameters, we initialize those variables to NUll.
+ // For IN parameters, we initialize those variables with their corresponding parameter of the command that executes the CALL
+ // statement.
+ for (var i = 0; i < command.ColumnModifications.Count; i++)
+ {
+ var columnModification = command.ColumnModifications[i];
+ var parameter = (IStoreStoredProcedureParameter)columnModification.Column!;
+
+ // MySQL stored procedures cannot return a regular result set, and output parameter values are simply sent back to us as the
+ // result set, if we append a SELECT query for them. This is very different from SQL Server, where output parameter values
+ // can be sent back in addition to result sets.
+ if (!parameter.Direction.HasFlag(ParameterDirection.Output))
+ {
+ continue;
+ }
+
+ // The distinction between having only a rows affected output parameter and having other non-rows affected parameters
+ // is important later on (i.e. whether we need to propagate or not).
+ resultSetMapping = parameter == command.RowsAffectedColumn &&
+ resultSetMapping == ResultSetMapping.NoResults
+ ? ResultSetMapping.ResultSetWithRowsAffectedOnly | ResultSetMapping.LastInResultSet
+ : ResultSetMapping.LastInResultSet;
+
+ commandStringBuilder.Append("SET ");
+
+ var commandParameterName = columnModification.UseOriginalValueParameter
+ ? columnModification.OriginalParameterName!
+ : columnModification.ParameterName!;
+
+ var procedureCallParameterName = GetProcedureCallOutParameterVariableName(commandParameterName);
+
+ SqlGenerationHelper.GenerateParameterNamePlaceholder(commandStringBuilder, procedureCallParameterName);
+
+ commandStringBuilder.Append(" = ");
+
+ if (parameter.Direction.HasFlag(ParameterDirection.Input))
+ {
+ SqlGenerationHelper.GenerateParameterNamePlaceholder(commandStringBuilder, commandParameterName);
+ }
+ else
+ {
+ commandStringBuilder.Append("NULL");
+ }
+
+ commandStringBuilder.AppendLine(SqlGenerationHelper.StatementTerminator);
+ }
+
+ commandStringBuilder.Append("CALL ");
+
+ // MySQL supports neither a return value nor a result set that gets returned from inside of a stored procedures. It only
+ // supports output parameters to propagate values back to the caller.
+ Check.DebugAssert(storedProcedure.ReturnValue is null, "storedProcedure.Return is null");
+ Check.DebugAssert(!storedProcedure.ResultColumns.Any(), "!storedProcedure.ResultColumns.Any()");
+
+ SqlGenerationHelper.DelimitIdentifier(commandStringBuilder, storedProcedure.Name, storedProcedure.Schema);
+
+ commandStringBuilder.Append('(');
+
+ // Only positional parameter style supported for now, see https://github.com/dotnet/efcore/issues/28439
+
+ // Note: the column modifications are already ordered according to the sproc parameter ordering
+ // (see ModificationCommand.GenerateColumnModifications)
+ for (var i = 0; i < command.ColumnModifications.Count; i++)
+ {
+ var columnModification = command.ColumnModifications[i];
+ var parameter = (IStoreStoredProcedureParameter)columnModification.Column!;
+
+ if (i > 0)
+ {
+ commandStringBuilder.Append(", ");
+ }
+
+ Check.DebugAssert(columnModification.UseParameter, "Column modification matched a parameter, but UseParameter is false");
+
+ var commandParameterName = columnModification.UseOriginalValueParameter
+ ? columnModification.OriginalParameterName!
+ : columnModification.ParameterName!;
+
+ var procedureCallParameterName = GetProcedureCallOutParameterVariableName(commandParameterName);
+
+ SqlGenerationHelper.GenerateParameterNamePlaceholder(
+ commandStringBuilder,
+ parameter.Direction.HasFlag(ParameterDirection.Output)
+ ? procedureCallParameterName
+ : commandParameterName);
+ }
+
+ commandStringBuilder.Append(')');
+ commandStringBuilder.AppendLine(SqlGenerationHelper.StatementTerminator);
+
+ // The CALL has propagated any INOUT and OUT values back into our previously declared variables.
+ // To get those values back to the caller, we need to run a SELECT statement against those variables.
+ // We start by checking, whether there exist any INOUT or OUT parameters.
+ if (resultSetMapping != ResultSetMapping.NoResults)
+ {
+ commandStringBuilder.Append("SELECT ");
+
+ var first = true;
+
+ for (var i = 0; i < command.ColumnModifications.Count; i++)
+ {
+ var columnModification = command.ColumnModifications[i];
+ var parameter = (IStoreStoredProcedureParameter)columnModification.Column!;
+
+ if (!parameter.Direction.HasFlag(ParameterDirection.Output))
+ {
+ continue;
+ }
+
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ commandStringBuilder.Append(", ");
+ }
+
+ var commandParameterName = columnModification.UseOriginalValueParameter
+ ? columnModification.OriginalParameterName!
+ : columnModification.ParameterName!;
+
+ var procedureCallParameterName = GetProcedureCallOutParameterVariableName(commandParameterName);
+
+ SqlGenerationHelper.GenerateParameterNamePlaceholder(
+ commandStringBuilder,
+ procedureCallParameterName);
+ }
+
+ commandStringBuilder.AppendLine(SqlGenerationHelper.StatementTerminator);
+ }
+
+ requiresTransaction = true;
+
+ return resultSetMapping;
+ }
+
+ ///
+ /// Returns the name (without the @ prefix) used for any temporary user variables, that need to be declared to get values out of a
+ /// stored procedure.
+ ///
+ /// The name of the parameter of the command that executes the CALL statement.
+ /// The variable name (without the @ prefix).
+ protected virtual string GetProcedureCallOutParameterVariableName(string commandParameterName)
+ => "_out_" + commandParameterName;
+
+ protected override bool IsIdentityOperation(IColumnModification modification)
+ {
+ var isIdentityOperation = base.IsIdentityOperation(modification);
+
+ if (isIdentityOperation &&
+ modification.Property is { } property)
+ {
+ var (tableName, schema) = GetTableNameAndSchema(modification, property);
+ var storeObject = StoreObjectIdentifier.Table(tableName, schema);
+
+ return property.GetValueGenerationStrategy(storeObject) is MySqlValueGenerationStrategy.IdentityColumn;
+ }
+
+ return isIdentityOperation;
+ }
+
+ private static (string tableName, string schema) GetTableNameAndSchema(IColumnModification modification, IProperty property)
+ {
+ if (modification.Column?.Table is { } table)
+ {
+ return (table.Name, table.Schema);
+ }
+ else
+ {
+ // CHECK: Is this branch ever hit and then returns something different than null, or can we just rely on
+ // `modification.Column?.Table`?
+ return (property.DeclaringEntityType.GetTableName(), property.DeclaringEntityType.GetSchema());
+ }
+ }
}
}
diff --git a/test/EFCore.MySql.FunctionalTests/BuiltInDataTypesMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/BuiltInDataTypesMySqlTest.cs
index bc1b4d9a7..a37332407 100644
--- a/test/EFCore.MySql.FunctionalTests/BuiltInDataTypesMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/BuiltInDataTypesMySqlTest.cs
@@ -723,7 +723,7 @@ private static void AssertMappedNullableDataTypes(MappedNullableDataTypes entity
Assert.Equal(81.1m, entity.DecimalAsDecimal);
Assert.Equal(82.2m, entity.DecimalAsFixed);
Assert.Equal(83.3, entity.DoubleAsReal.Value, 1);
- Assert.Equal(84.4f, entity.FloatAsFloat.Value, 1);
+ Assert.Equal(84.4f, entity.FloatAsFloat.Value, 0.1f);
Assert.Equal(85.5, entity.DoubleAsDoublePrecision.Value, 1);
Assert.Equal(new DateTime(2015, 1, 2), entity.DateTimeAsDate);
Assert.Equal(new DateTimeOffset(new DateTime(2016, 1, 2, 11, 11, 12), TimeSpan.Zero), entity.DateTimeOffsetAsDatetime);
@@ -873,6 +873,84 @@ public virtual void Can_insert_and_read_back_all_mapped_data_types_set_to_null()
}
}
+ // Overridden because of TestNullableDateTimeOffset, since MySQL does not offer a native data type to save a date/time with
+ // timezone.
+ public override void Can_insert_and_read_back_all_nullable_data_types_with_values_set_to_non_null()
+ {
+ using (var context = CreateContext())
+ {
+ context.Set().Add(
+ new BuiltInNullableDataTypes
+ {
+ Id = 101,
+ PartitionId = 101,
+ TestString = "TestString",
+ TestByteArray = new byte[] { 10, 9, 8, 7, 6 },
+ TestNullableInt16 = -1234,
+ TestNullableInt32 = -123456789,
+ TestNullableInt64 = -1234567890123456789L,
+ TestNullableDouble = -1.23456789,
+ TestNullableDecimal = -1234567890.01M,
+ TestNullableDateTime = DateTime.Parse("01/01/2000 12:34:56").ToUniversalTime(),
+ TestNullableDateTimeOffset = new DateTimeOffset(DateTime.Parse("01/01/2000 12:34:56"), TimeSpan.FromHours(-8.0)),
+ TestNullableTimeSpan = new TimeSpan(0, 10, 9, 8, 7),
+ TestNullableSingle = -1.234F,
+ TestNullableBoolean = false,
+ TestNullableByte = 255,
+ TestNullableUnsignedInt16 = 1234,
+ TestNullableUnsignedInt32 = 1234565789U,
+ TestNullableUnsignedInt64 = 1234567890123456789UL,
+ TestNullableCharacter = 'a',
+ TestNullableSignedByte = -128,
+ Enum64 = Enum64.SomeValue,
+ Enum32 = Enum32.SomeValue,
+ Enum16 = Enum16.SomeValue,
+ Enum8 = Enum8.SomeValue,
+ EnumU64 = EnumU64.SomeValue,
+ EnumU32 = EnumU32.SomeValue,
+ EnumU16 = EnumU16.SomeValue,
+ EnumS8 = EnumS8.SomeValue
+ });
+
+ Assert.Equal(1, context.SaveChanges());
+ }
+
+ using (var context = CreateContext())
+ {
+ var dt = context.Set().Where(ndt => ndt.Id == 101).ToList().Single();
+
+ var entityType = context.Model.FindEntityType(typeof(BuiltInNullableDataTypes));
+ AssertEqualIfMapped(entityType, "TestString", () => dt.TestString);
+ AssertEqualIfMapped(entityType, new byte[] { 10, 9, 8, 7, 6 }, () => dt.TestByteArray);
+ AssertEqualIfMapped(entityType, (short)-1234, () => dt.TestNullableInt16);
+ AssertEqualIfMapped(entityType, -123456789, () => dt.TestNullableInt32);
+ AssertEqualIfMapped(entityType, -1234567890123456789L, () => dt.TestNullableInt64);
+ AssertEqualIfMapped(entityType, -1.23456789, () => dt.TestNullableDouble);
+ AssertEqualIfMapped(entityType, -1234567890.01M, () => dt.TestNullableDecimal);
+ AssertEqualIfMapped(entityType, DateTime.Parse("01/01/2000 12:34:56").ToUniversalTime(), () => dt.TestNullableDateTime);
+ AssertEqualIfMapped(
+ entityType, new DateTimeOffset(DateTime.Parse("01/01/2000 12:34:56"), TimeSpan.FromHours(-8.0)).ToUniversalTime(), // adjusted for Pomelo's translation
+ () => dt.TestNullableDateTimeOffset);
+ AssertEqualIfMapped(entityType, new TimeSpan(0, 10, 9, 8, 7), () => dt.TestNullableTimeSpan);
+ AssertEqualIfMapped(entityType, -1.234F, () => dt.TestNullableSingle);
+ AssertEqualIfMapped(entityType, false, () => dt.TestNullableBoolean);
+ AssertEqualIfMapped(entityType, (byte)255, () => dt.TestNullableByte);
+ AssertEqualIfMapped(entityType, Enum64.SomeValue, () => dt.Enum64);
+ AssertEqualIfMapped(entityType, Enum32.SomeValue, () => dt.Enum32);
+ AssertEqualIfMapped(entityType, Enum16.SomeValue, () => dt.Enum16);
+ AssertEqualIfMapped(entityType, Enum8.SomeValue, () => dt.Enum8);
+ AssertEqualIfMapped(entityType, (ushort)1234, () => dt.TestNullableUnsignedInt16);
+ AssertEqualIfMapped(entityType, 1234565789U, () => dt.TestNullableUnsignedInt32);
+ AssertEqualIfMapped(entityType, 1234567890123456789UL, () => dt.TestNullableUnsignedInt64);
+ AssertEqualIfMapped(entityType, 'a', () => dt.TestNullableCharacter);
+ AssertEqualIfMapped(entityType, (sbyte)-128, () => dt.TestNullableSignedByte);
+ AssertEqualIfMapped(entityType, EnumU64.SomeValue, () => dt.EnumU64);
+ AssertEqualIfMapped(entityType, EnumU32.SomeValue, () => dt.EnumU32);
+ AssertEqualIfMapped(entityType, EnumU16.SomeValue, () => dt.EnumU16);
+ AssertEqualIfMapped(entityType, EnumS8.SomeValue, () => dt.EnumS8);
+ }
+ }
+
private static void AssertNullMappedNullableDataTypes(MappedNullableDataTypes entity, int id)
{
Assert.Equal(id, entity.Int);
diff --git a/test/EFCore.MySql.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesMySqlFixture.cs b/test/EFCore.MySql.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesMySqlFixture.cs
new file mode 100644
index 000000000..aa5e22242
--- /dev/null
+++ b/test/EFCore.MySql.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesMySqlFixture.cs
@@ -0,0 +1,10 @@
+namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests.BulkUpdates;
+
+public class FiltersInheritanceBulkUpdatesMySqlFixture : InheritanceBulkUpdatesMySqlFixture
+{
+ protected override string StoreName
+ => "FiltersInheritanceBulkUpdatesTest";
+
+ protected override bool EnableFilters
+ => true;
+}
diff --git a/test/EFCore.MySql.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesMySqlTest.cs
new file mode 100644
index 000000000..f08b7ef21
--- /dev/null
+++ b/test/EFCore.MySql.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesMySqlTest.cs
@@ -0,0 +1,222 @@
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore.BulkUpdates;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+using MySqlConnector;
+using Xunit;
+
+namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests.BulkUpdates;
+
+public class FiltersInheritanceBulkUpdatesMySqlTest : FiltersInheritanceBulkUpdatesTestBase<
+ FiltersInheritanceBulkUpdatesMySqlFixture>
+{
+ public FiltersInheritanceBulkUpdatesMySqlTest(FiltersInheritanceBulkUpdatesMySqlFixture fixture)
+ : base(fixture)
+ {
+ ClearLog();
+ }
+
+ [ConditionalFact]
+ public virtual void Check_all_tests_overridden()
+ => TestHelpers.AssertAllMethodsOverridden(GetType());
+
+ public override async Task Delete_where_hierarchy(bool async)
+ {
+ await base.Delete_where_hierarchy(async);
+
+ AssertSql(
+"""
+DELETE `a`
+FROM `Animals` AS `a`
+WHERE (`a`.`CountryId` = 1) AND (`a`.`Name` = 'Great spotted kiwi')
+""");
+ }
+
+ public override async Task Delete_where_hierarchy_derived(bool async)
+ {
+ await base.Delete_where_hierarchy_derived(async);
+
+ AssertSql(
+"""
+DELETE `a`
+FROM `Animals` AS `a`
+WHERE ((`a`.`Discriminator` = 'Kiwi') AND (`a`.`CountryId` = 1)) AND (`a`.`Name` = 'Great spotted kiwi')
+""");
+ }
+
+ public override async Task Delete_where_using_hierarchy(bool async)
+ {
+ await base.Delete_where_using_hierarchy(async);
+
+ AssertSql(
+"""
+DELETE `c`
+FROM `Countries` AS `c`
+WHERE (
+ SELECT COUNT(*)
+ FROM `Animals` AS `a`
+ WHERE ((`a`.`CountryId` = 1) AND (`c`.`Id` = `a`.`CountryId`)) AND (`a`.`CountryId` > 0)) > 0
+""");
+ }
+
+ public override async Task Delete_where_using_hierarchy_derived(bool async)
+ {
+ await base.Delete_where_using_hierarchy_derived(async);
+
+ AssertSql(
+"""
+DELETE `c`
+FROM `Countries` AS `c`
+WHERE (
+ SELECT COUNT(*)
+ FROM `Animals` AS `a`
+ WHERE (((`a`.`CountryId` = 1) AND (`c`.`Id` = `a`.`CountryId`)) AND (`a`.`Discriminator` = 'Kiwi')) AND (`a`.`CountryId` > 0)) > 0
+""");
+ }
+
+ public override async Task Delete_GroupBy_Where_Select_First(bool async)
+ {
+ await base.Delete_GroupBy_Where_Select_First(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_GroupBy_Where_Select_First_2(bool async)
+ {
+ await base.Delete_GroupBy_Where_Select_First_2(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_GroupBy_Where_Select_First_3(bool async)
+ {
+ // Not supported by MySQL:
+ // Error Code: 1093. You can't specify target table 'c' for update in FROM clause
+ await Assert.ThrowsAsync(
+ () => base.Delete_GroupBy_Where_Select_First_3(async));
+
+ AssertSql(
+"""
+DELETE `a`
+FROM `Animals` AS `a`
+WHERE (`a`.`CountryId` = 1) AND EXISTS (
+ SELECT 1
+ FROM `Animals` AS `a0`
+ WHERE `a0`.`CountryId` = 1
+ GROUP BY `a0`.`CountryId`
+ HAVING (COUNT(*) < 3) AND ((
+ SELECT `a1`.`Id`
+ FROM `Animals` AS `a1`
+ WHERE (`a1`.`CountryId` = 1) AND (`a0`.`CountryId` = `a1`.`CountryId`)
+ LIMIT 1) = `a`.`Id`))
+"""
+);
+ }
+
+ public override async Task Delete_where_keyless_entity_mapped_to_sql_query(bool async)
+ {
+ await base.Delete_where_keyless_entity_mapped_to_sql_query(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_where_hierarchy_subquery(bool async)
+ {
+ await base.Delete_where_hierarchy_subquery(async);
+
+ AssertSql(
+"""
+@__p_1='3'
+@__p_0='0'
+
+DELETE `a`
+FROM `Animals` AS `a`
+WHERE EXISTS (
+ SELECT 1
+ FROM (
+ SELECT `a0`.`Id`, `a0`.`CountryId`, `a0`.`Discriminator`, `a0`.`Name`, `a0`.`Species`, `a0`.`EagleId`, `a0`.`IsFlightless`, `a0`.`Group`, `a0`.`FoundOn`
+ FROM `Animals` AS `a0`
+ WHERE (`a0`.`CountryId` = 1) AND (`a0`.`Name` = 'Great spotted kiwi')
+ ORDER BY `a0`.`Name`
+ LIMIT @__p_1 OFFSET @__p_0
+ ) AS `t`
+ WHERE `t`.`Id` = `a`.`Id`)
+""");
+ }
+
+ public override async Task Update_where_hierarchy(bool async)
+ {
+ await base.Update_where_hierarchy(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Animals` AS `a`
+SET `a`.`Name` = 'Animal'
+WHERE (`a`.`CountryId` = 1) AND (`a`.`Name` = 'Great spotted kiwi')
+""");
+ }
+
+ public override async Task Update_where_hierarchy_subquery(bool async)
+ {
+ await base.Update_where_hierarchy_subquery(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ public override async Task Update_where_hierarchy_derived(bool async)
+ {
+ await base.Update_where_hierarchy_derived(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Animals` AS `a`
+SET `a`.`Name` = 'Kiwi'
+WHERE ((`a`.`Discriminator` = 'Kiwi') AND (`a`.`CountryId` = 1)) AND (`a`.`Name` = 'Great spotted kiwi')
+""");
+ }
+
+ public override async Task Update_where_using_hierarchy(bool async)
+ {
+ await base.Update_where_using_hierarchy(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Countries` AS `c`
+SET `c`.`Name` = 'Monovia'
+WHERE (
+ SELECT COUNT(*)
+ FROM `Animals` AS `a`
+ WHERE ((`a`.`CountryId` = 1) AND (`c`.`Id` = `a`.`CountryId`)) AND (`a`.`CountryId` > 0)) > 0
+""");
+ }
+
+ public override async Task Update_where_using_hierarchy_derived(bool async)
+ {
+ await base.Update_where_using_hierarchy_derived(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Countries` AS `c`
+SET `c`.`Name` = 'Monovia'
+WHERE (
+ SELECT COUNT(*)
+ FROM `Animals` AS `a`
+ WHERE (((`a`.`CountryId` = 1) AND (`c`.`Id` = `a`.`CountryId`)) AND (`a`.`Discriminator` = 'Kiwi')) AND (`a`.`CountryId` > 0)) > 0
+""");
+ }
+
+ public override async Task Update_where_keyless_entity_mapped_to_sql_query(bool async)
+ {
+ await base.Update_where_keyless_entity_mapped_to_sql_query(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ protected override void ClearLog()
+ => Fixture.TestSqlLoggerFactory.Clear();
+
+ private void AssertSql(params string[] expected)
+ => Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
+
+ private void AssertExecuteUpdateSql(params string[] expected)
+ => Fixture.TestSqlLoggerFactory.AssertBaseline(expected, forUpdate: true);
+}
diff --git a/test/EFCore.MySql.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesMySqlFixture.cs b/test/EFCore.MySql.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesMySqlFixture.cs
new file mode 100644
index 000000000..2b395c574
--- /dev/null
+++ b/test/EFCore.MySql.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesMySqlFixture.cs
@@ -0,0 +1,11 @@
+using Microsoft.EntityFrameworkCore.BulkUpdates;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+using Pomelo.EntityFrameworkCore.MySql.FunctionalTests.TestUtilities;
+
+namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests.BulkUpdates;
+
+public class InheritanceBulkUpdatesMySqlFixture : InheritanceBulkUpdatesRelationalFixture
+{
+ protected override ITestStoreFactory TestStoreFactory
+ => MySqlTestStoreFactory.Instance;
+}
diff --git a/test/EFCore.MySql.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesMySqlTest.cs
new file mode 100644
index 000000000..dead015ea
--- /dev/null
+++ b/test/EFCore.MySql.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesMySqlTest.cs
@@ -0,0 +1,219 @@
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore.BulkUpdates;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+using MySqlConnector;
+using Xunit;
+
+namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests.BulkUpdates;
+
+public class InheritanceBulkUpdatesMySqlTest : InheritanceBulkUpdatesTestBase
+{
+ public InheritanceBulkUpdatesMySqlTest(InheritanceBulkUpdatesMySqlFixture fixture)
+ : base(fixture)
+ {
+ ClearLog();
+ }
+
+ [ConditionalFact]
+ public virtual void Check_all_tests_overridden()
+ => TestHelpers.AssertAllMethodsOverridden(GetType());
+
+ public override async Task Delete_where_hierarchy(bool async)
+ {
+ await base.Delete_where_hierarchy(async);
+
+ AssertSql(
+"""
+DELETE `a`
+FROM `Animals` AS `a`
+WHERE `a`.`Name` = 'Great spotted kiwi'
+""");
+ }
+
+ public override async Task Delete_where_hierarchy_derived(bool async)
+ {
+ await base.Delete_where_hierarchy_derived(async);
+
+ AssertSql(
+"""
+DELETE `a`
+FROM `Animals` AS `a`
+WHERE (`a`.`Discriminator` = 'Kiwi') AND (`a`.`Name` = 'Great spotted kiwi')
+""");
+ }
+
+ public override async Task Delete_where_using_hierarchy(bool async)
+ {
+ await base.Delete_where_using_hierarchy(async);
+
+ AssertSql(
+"""
+DELETE `c`
+FROM `Countries` AS `c`
+WHERE (
+ SELECT COUNT(*)
+ FROM `Animals` AS `a`
+ WHERE (`c`.`Id` = `a`.`CountryId`) AND (`a`.`CountryId` > 0)) > 0
+""");
+ }
+
+ public override async Task Delete_where_using_hierarchy_derived(bool async)
+ {
+ await base.Delete_where_using_hierarchy_derived(async);
+
+ AssertSql(
+"""
+DELETE `c`
+FROM `Countries` AS `c`
+WHERE (
+ SELECT COUNT(*)
+ FROM `Animals` AS `a`
+ WHERE ((`c`.`Id` = `a`.`CountryId`) AND (`a`.`Discriminator` = 'Kiwi')) AND (`a`.`CountryId` > 0)) > 0
+""");
+ }
+
+ public override async Task Delete_GroupBy_Where_Select_First(bool async)
+ {
+ await base.Delete_GroupBy_Where_Select_First(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_GroupBy_Where_Select_First_2(bool async)
+ {
+ await base.Delete_GroupBy_Where_Select_First_2(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_GroupBy_Where_Select_First_3(bool async)
+ {
+ // Not supported by MySQL:
+ // Error Code: 1093. You can't specify target table 'c' for update in FROM clause
+ await Assert.ThrowsAsync(
+ () => base.Delete_GroupBy_Where_Select_First_3(async));
+
+ AssertSql(
+"""
+DELETE `a`
+FROM `Animals` AS `a`
+WHERE EXISTS (
+ SELECT 1
+ FROM `Animals` AS `a0`
+ GROUP BY `a0`.`CountryId`
+ HAVING (COUNT(*) < 3) AND ((
+ SELECT `a1`.`Id`
+ FROM `Animals` AS `a1`
+ WHERE `a0`.`CountryId` = `a1`.`CountryId`
+ LIMIT 1) = `a`.`Id`))
+""");
+ }
+
+ public override async Task Delete_where_keyless_entity_mapped_to_sql_query(bool async)
+ {
+ await base.Delete_where_keyless_entity_mapped_to_sql_query(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_where_hierarchy_subquery(bool async)
+ {
+ await base.Delete_where_hierarchy_subquery(async);
+
+ AssertSql(
+"""
+@__p_1='3'
+@__p_0='0'
+
+DELETE `a`
+FROM `Animals` AS `a`
+WHERE EXISTS (
+ SELECT 1
+ FROM (
+ SELECT `a0`.`Id`, `a0`.`CountryId`, `a0`.`Discriminator`, `a0`.`Name`, `a0`.`Species`, `a0`.`EagleId`, `a0`.`IsFlightless`, `a0`.`Group`, `a0`.`FoundOn`
+ FROM `Animals` AS `a0`
+ WHERE `a0`.`Name` = 'Great spotted kiwi'
+ ORDER BY `a0`.`Name`
+ LIMIT @__p_1 OFFSET @__p_0
+ ) AS `t`
+ WHERE `t`.`Id` = `a`.`Id`)
+""");
+ }
+
+ public override async Task Update_where_hierarchy(bool async)
+ {
+ await base.Update_where_hierarchy(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Animals` AS `a`
+SET `a`.`Name` = 'Animal'
+WHERE `a`.`Name` = 'Great spotted kiwi'
+""");
+ }
+
+ public override async Task Update_where_hierarchy_subquery(bool async)
+ {
+ await base.Update_where_hierarchy_subquery(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ public override async Task Update_where_hierarchy_derived(bool async)
+ {
+ await base.Update_where_hierarchy_derived(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Animals` AS `a`
+SET `a`.`Name` = 'Kiwi'
+WHERE (`a`.`Discriminator` = 'Kiwi') AND (`a`.`Name` = 'Great spotted kiwi')
+""");
+ }
+
+ public override async Task Update_where_using_hierarchy(bool async)
+ {
+ await base.Update_where_using_hierarchy(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Countries` AS `c`
+SET `c`.`Name` = 'Monovia'
+WHERE (
+ SELECT COUNT(*)
+ FROM `Animals` AS `a`
+ WHERE (`c`.`Id` = `a`.`CountryId`) AND (`a`.`CountryId` > 0)) > 0
+""");
+ }
+
+ public override async Task Update_where_using_hierarchy_derived(bool async)
+ {
+ await base.Update_where_using_hierarchy_derived(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Countries` AS `c`
+SET `c`.`Name` = 'Monovia'
+WHERE (
+ SELECT COUNT(*)
+ FROM `Animals` AS `a`
+ WHERE ((`c`.`Id` = `a`.`CountryId`) AND (`a`.`Discriminator` = 'Kiwi')) AND (`a`.`CountryId` > 0)) > 0
+""");
+ }
+
+ public override async Task Update_where_keyless_entity_mapped_to_sql_query(bool async)
+ {
+ await base.Update_where_keyless_entity_mapped_to_sql_query(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ protected override void ClearLog()
+ => Fixture.TestSqlLoggerFactory.Clear();
+
+ private void AssertSql(params string[] expected)
+ => Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
+
+ private void AssertExecuteUpdateSql(params string[] expected)
+ => Fixture.TestSqlLoggerFactory.AssertBaseline(expected, forUpdate: true);
+}
diff --git a/test/EFCore.MySql.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesMySqlTest.cs
new file mode 100644
index 000000000..df9c72c19
--- /dev/null
+++ b/test/EFCore.MySql.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesMySqlTest.cs
@@ -0,0 +1,65 @@
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore.BulkUpdates;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+using Pomelo.EntityFrameworkCore.MySql.FunctionalTests.TestUtilities;
+using Xunit;
+
+namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests.BulkUpdates;
+
+public class NonSharedModelBulkUpdatesMySqlTest : NonSharedModelBulkUpdatesTestBase
+{
+ protected override ITestStoreFactory TestStoreFactory
+ => MySqlTestStoreFactory.Instance;
+
+ [ConditionalFact]
+ public virtual void Check_all_tests_overridden()
+ => TestHelpers.AssertAllMethodsOverridden(GetType());
+
+ public override async Task Delete_aggregate_root_when_eager_loaded_owned_collection(bool async)
+ {
+ await base.Delete_aggregate_root_when_eager_loaded_owned_collection(async);
+
+ AssertSql(
+"""
+DELETE `o`
+FROM `Owner` AS `o`
+""");
+ }
+
+ public override async Task Delete_aggregate_root_when_table_sharing_with_owned(bool async)
+ {
+ await base.Delete_aggregate_root_when_table_sharing_with_owned(async);
+
+ AssertSql(
+"""
+DELETE `o`
+FROM `Owner` AS `o`
+""");
+ }
+
+ public override async Task Delete_aggregate_root_when_table_sharing_with_non_owned_throws(bool async)
+ {
+ await base.Delete_aggregate_root_when_table_sharing_with_non_owned_throws(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_predicate_based_on_optional_navigation(bool async)
+ {
+ await base.Delete_predicate_based_on_optional_navigation(async);
+
+ AssertSql(
+"""
+DELETE `p`
+FROM `Posts` AS `p`
+LEFT JOIN `Blogs` AS `b` ON `p`.`BlogId` = `b`.`Id`
+WHERE `b`.`Title` IS NOT NULL AND (`b`.`Title` LIKE 'Arthur%')
+""");
+ }
+
+ private void AssertSql(params string[] expected)
+ => TestSqlLoggerFactory.AssertBaseline(expected);
+
+ private void AssertExecuteUpdateSql(params string[] expected)
+ => TestSqlLoggerFactory.AssertBaseline(expected, forUpdate: true);
+}
diff --git a/test/EFCore.MySql.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesMySqlFixture.cs b/test/EFCore.MySql.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesMySqlFixture.cs
new file mode 100644
index 000000000..6808823e4
--- /dev/null
+++ b/test/EFCore.MySql.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesMySqlFixture.cs
@@ -0,0 +1,13 @@
+using Microsoft.EntityFrameworkCore.BulkUpdates;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+using Pomelo.EntityFrameworkCore.MySql.FunctionalTests.TestUtilities;
+
+namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests.BulkUpdates;
+
+public class NorthwindBulkUpdatesMySqlFixture : NorthwindBulkUpdatesFixture
+ where TModelCustomizer : IModelCustomizer, new()
+{
+ protected override ITestStoreFactory TestStoreFactory
+ => MySqlNorthwindTestStoreFactory.Instance;
+}
diff --git a/test/EFCore.MySql.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesMySqlTest.cs
new file mode 100644
index 000000000..b386e4533
--- /dev/null
+++ b/test/EFCore.MySql.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesMySqlTest.cs
@@ -0,0 +1,1444 @@
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore.BulkUpdates;
+using Microsoft.EntityFrameworkCore.TestModels.Northwind;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+using MySqlConnector;
+using Pomelo.EntityFrameworkCore.MySql.Infrastructure;
+using Pomelo.EntityFrameworkCore.MySql.Tests;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests.BulkUpdates;
+
+public class NorthwindBulkUpdatesMySqlTest : NorthwindBulkUpdatesTestBase>
+{
+ public NorthwindBulkUpdatesMySqlTest(
+ NorthwindBulkUpdatesMySqlFixture fixture,
+ ITestOutputHelper testOutputHelper)
+ : base(fixture)
+ {
+ ClearLog();
+ // Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
+ }
+
+ [ConditionalFact]
+ public virtual void Check_all_tests_overridden()
+ => TestHelpers.AssertAllMethodsOverridden(GetType());
+
+ public override async Task Delete_Where_TagWith(bool async)
+ {
+ await base.Delete_Where_TagWith(async);
+
+ AssertSql(
+"""
+-- MyDelete
+
+DELETE `o`
+FROM `Order Details` AS `o`
+WHERE `o`.`OrderID` < 10300
+""");
+ }
+
+ public override async Task Delete_Where(bool async)
+ {
+ await base.Delete_Where(async);
+
+ AssertSql(
+"""
+DELETE `o`
+FROM `Order Details` AS `o`
+WHERE `o`.`OrderID` < 10300
+""");
+ }
+
+ public override async Task Delete_Where_parameter(bool async)
+ {
+ await base.Delete_Where_parameter(async);
+
+ AssertSql(
+"""
+@__quantity_0='1' (Nullable = true) (DbType = Int16)
+
+DELETE `o`
+FROM `Order Details` AS `o`
+WHERE `o`.`Quantity` = @__quantity_0
+""",
+ //
+ """
+DELETE `o`
+FROM `Order Details` AS `o`
+WHERE FALSE
+""");
+ }
+
+ public override async Task Delete_Where_OrderBy(bool async)
+ {
+ await base.Delete_Where_OrderBy(async);
+
+ AssertSql(
+"""
+DELETE
+FROM `Order Details`
+WHERE `OrderID` < 10300
+ORDER BY `OrderID`
+""");
+ }
+
+ public override async Task Delete_Where_OrderBy_Skip(bool async)
+ {
+ await base.Delete_Where_OrderBy_Skip(async);
+
+ AssertSql(
+"""
+@__p_0='100'
+
+DELETE `o`
+FROM `Order Details` AS `o`
+WHERE EXISTS (
+ SELECT 1
+ FROM (
+ SELECT `o0`.`OrderID`, `o0`.`ProductID`, `o0`.`Discount`, `o0`.`Quantity`, `o0`.`UnitPrice`
+ FROM `Order Details` AS `o0`
+ WHERE `o0`.`OrderID` < 10300
+ ORDER BY `o0`.`OrderID`
+ LIMIT 18446744073709551610 OFFSET @__p_0
+ ) AS `t`
+ WHERE (`t`.`OrderID` = `o`.`OrderID`) AND (`t`.`ProductID` = `o`.`ProductID`))
+""");
+ }
+
+ public override async Task Delete_Where_OrderBy_Take(bool async)
+ {
+ await base.Delete_Where_OrderBy_Take(async);
+
+ AssertSql(
+"""
+@__p_0='100'
+
+DELETE
+FROM `Order Details`
+WHERE `OrderID` < 10300
+ORDER BY `OrderID`
+LIMIT @__p_0
+""");
+ }
+
+ public override async Task Delete_Where_OrderBy_Skip_Take(bool async)
+ {
+ await base.Delete_Where_OrderBy_Skip_Take(async);
+
+ AssertSql(
+"""
+@__p_0='100'
+
+DELETE `o`
+FROM `Order Details` AS `o`
+WHERE EXISTS (
+ SELECT 1
+ FROM (
+ SELECT `o0`.`OrderID`, `o0`.`ProductID`, `o0`.`Discount`, `o0`.`Quantity`, `o0`.`UnitPrice`
+ FROM `Order Details` AS `o0`
+ WHERE `o0`.`OrderID` < 10300
+ ORDER BY `o0`.`OrderID`
+ LIMIT @__p_0 OFFSET @__p_0
+ ) AS `t`
+ WHERE (`t`.`OrderID` = `o`.`OrderID`) AND (`t`.`ProductID` = `o`.`ProductID`))
+""");
+ }
+
+ public override async Task Delete_Where_Skip(bool async)
+ {
+ await base.Delete_Where_Skip(async);
+
+ AssertSql(
+"""
+@__p_0='100'
+
+DELETE `o`
+FROM `Order Details` AS `o`
+WHERE EXISTS (
+ SELECT 1
+ FROM (
+ SELECT `o0`.`OrderID`, `o0`.`ProductID`, `o0`.`Discount`, `o0`.`Quantity`, `o0`.`UnitPrice`
+ FROM `Order Details` AS `o0`
+ WHERE `o0`.`OrderID` < 10300
+ LIMIT 18446744073709551610 OFFSET @__p_0
+ ) AS `t`
+ WHERE (`t`.`OrderID` = `o`.`OrderID`) AND (`t`.`ProductID` = `o`.`ProductID`))
+""");
+ }
+
+ public override async Task Delete_Where_Take(bool async)
+ {
+ await base.Delete_Where_Take(async);
+
+ AssertSql(
+"""
+@__p_0='100'
+
+DELETE
+FROM `Order Details`
+WHERE `OrderID` < 10300
+LIMIT @__p_0
+""");
+ }
+
+ public override async Task Delete_Where_Skip_Take(bool async)
+ {
+ await base.Delete_Where_Skip_Take(async);
+
+ AssertSql(
+"""
+@__p_0='100'
+
+DELETE `o`
+FROM `Order Details` AS `o`
+WHERE EXISTS (
+ SELECT 1
+ FROM (
+ SELECT `o0`.`OrderID`, `o0`.`ProductID`, `o0`.`Discount`, `o0`.`Quantity`, `o0`.`UnitPrice`
+ FROM `Order Details` AS `o0`
+ WHERE `o0`.`OrderID` < 10300
+ LIMIT @__p_0 OFFSET @__p_0
+ ) AS `t`
+ WHERE (`t`.`OrderID` = `o`.`OrderID`) AND (`t`.`ProductID` = `o`.`ProductID`))
+""");
+ }
+
+ public override async Task Delete_Where_predicate_with_GroupBy_aggregate(bool async)
+ {
+ await base.Delete_Where_predicate_with_GroupBy_aggregate(async);
+
+ AssertSql(
+"""
+DELETE `o`
+FROM `Order Details` AS `o`
+WHERE `o`.`OrderID` < (
+ SELECT (
+ SELECT `o1`.`OrderID`
+ FROM `Orders` AS `o1`
+ WHERE (`o0`.`CustomerID` = `o1`.`CustomerID`) OR (`o0`.`CustomerID` IS NULL AND (`o1`.`CustomerID` IS NULL))
+ LIMIT 1)
+ FROM `Orders` AS `o0`
+ GROUP BY `o0`.`CustomerID`
+ HAVING COUNT(*) > 11
+ LIMIT 1)
+""");
+ }
+
+ public override async Task Delete_Where_predicate_with_GroupBy_aggregate_2(bool async)
+ {
+ await base.Delete_Where_predicate_with_GroupBy_aggregate_2(async);
+
+ AssertSql(
+"""
+DELETE `o`
+FROM `Order Details` AS `o`
+INNER JOIN `Orders` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+WHERE EXISTS (
+ SELECT 1
+ FROM `Orders` AS `o1`
+ GROUP BY `o1`.`CustomerID`
+ HAVING (COUNT(*) > 9) AND ((
+ SELECT `o2`.`OrderID`
+ FROM `Orders` AS `o2`
+ WHERE (`o1`.`CustomerID` = `o2`.`CustomerID`) OR (`o1`.`CustomerID` IS NULL AND (`o2`.`CustomerID` IS NULL))
+ LIMIT 1) = `o0`.`OrderID`))
+""");
+ }
+
+ public override async Task Delete_GroupBy_Where_Select(bool async)
+ {
+ await base.Delete_GroupBy_Where_Select(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_GroupBy_Where_Select_2(bool async)
+ {
+ await base.Delete_GroupBy_Where_Select_2(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_Where_Skip_Take_Skip_Take_causing_subquery(bool async)
+ {
+ await base.Delete_Where_Skip_Take_Skip_Take_causing_subquery(async);
+
+ AssertSql(
+"""
+@__p_0='100'
+@__p_2='5'
+@__p_1='20'
+
+DELETE `o`
+FROM `Order Details` AS `o`
+WHERE EXISTS (
+ SELECT 1
+ FROM (
+ SELECT `t`.`OrderID`, `t`.`ProductID`, `t`.`Discount`, `t`.`Quantity`, `t`.`UnitPrice`
+ FROM (
+ SELECT `o0`.`OrderID`, `o0`.`ProductID`, `o0`.`Discount`, `o0`.`Quantity`, `o0`.`UnitPrice`
+ FROM `Order Details` AS `o0`
+ WHERE `o0`.`OrderID` < 10300
+ LIMIT @__p_0 OFFSET @__p_0
+ ) AS `t`
+ LIMIT @__p_2 OFFSET @__p_1
+ ) AS `t0`
+ WHERE (`t0`.`OrderID` = `o`.`OrderID`) AND (`t0`.`ProductID` = `o`.`ProductID`))
+""");
+ }
+
+ public override async Task Delete_Where_Distinct(bool async)
+ {
+ await base.Delete_Where_Distinct(async);
+
+ AssertSql(
+"""
+DELETE `o`
+FROM `Order Details` AS `o`
+WHERE `o`.`OrderID` < 10300
+""");
+ }
+
+ public override async Task Delete_SelectMany(bool async)
+ {
+ await base.Delete_SelectMany(async);
+
+ AssertSql(
+"""
+DELETE `o0`
+FROM `Orders` AS `o`
+INNER JOIN `Order Details` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+WHERE `o`.`OrderID` < 10250
+""");
+ }
+
+ public override async Task Delete_SelectMany_subquery(bool async)
+ {
+ await base.Delete_SelectMany_subquery(async);
+
+ AssertSql(
+"""
+DELETE `o`
+FROM `Order Details` AS `o`
+WHERE EXISTS (
+ SELECT 1
+ FROM `Orders` AS `o0`
+ INNER JOIN (
+ SELECT `o1`.`OrderID`, `o1`.`ProductID`, `o1`.`Discount`, `o1`.`Quantity`, `o1`.`UnitPrice`
+ FROM `Order Details` AS `o1`
+ WHERE `o1`.`ProductID` > 0
+ ) AS `t` ON `o0`.`OrderID` = `t`.`OrderID`
+ WHERE (`o0`.`OrderID` < 10250) AND ((`t`.`OrderID` = `o`.`OrderID`) AND (`t`.`ProductID` = `o`.`ProductID`)))
+""");
+ }
+
+ public override async Task Delete_Where_using_navigation(bool async)
+ {
+ await base.Delete_Where_using_navigation(async);
+
+ AssertSql(
+"""
+DELETE `o`
+FROM `Order Details` AS `o`
+INNER JOIN `Orders` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+WHERE EXTRACT(year FROM `o0`.`OrderDate`) = 2000
+""");
+ }
+
+ public override async Task Delete_Where_using_navigation_2(bool async)
+ {
+ await base.Delete_Where_using_navigation_2(async);
+
+ AssertSql(
+"""
+DELETE `o`
+FROM `Order Details` AS `o`
+INNER JOIN `Orders` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+LEFT JOIN `Customers` AS `c` ON `o0`.`CustomerID` = `c`.`CustomerID`
+WHERE `c`.`CustomerID` IS NOT NULL AND (`c`.`CustomerID` LIKE 'F%')
+""");
+ }
+
+ public override async Task Delete_Union(bool async)
+ {
+ await base.Delete_Union(async);
+
+ AssertSql(
+"""
+DELETE `o`
+FROM `Order Details` AS `o`
+WHERE EXISTS (
+ SELECT 1
+ FROM (
+ SELECT `o0`.`OrderID`, `o0`.`ProductID`, `o0`.`Discount`, `o0`.`Quantity`, `o0`.`UnitPrice`
+ FROM `Order Details` AS `o0`
+ WHERE `o0`.`OrderID` < 10250
+ UNION
+ SELECT `o1`.`OrderID`, `o1`.`ProductID`, `o1`.`Discount`, `o1`.`Quantity`, `o1`.`UnitPrice`
+ FROM `Order Details` AS `o1`
+ WHERE `o1`.`OrderID` > 11250
+ ) AS `t`
+ WHERE (`t`.`OrderID` = `o`.`OrderID`) AND (`t`.`ProductID` = `o`.`ProductID`))
+""");
+ }
+
+ public override async Task Delete_Concat(bool async)
+ {
+ await base.Delete_Concat(async);
+
+ AssertSql(
+"""
+DELETE `o`
+FROM `Order Details` AS `o`
+WHERE EXISTS (
+ SELECT 1
+ FROM (
+ SELECT `o0`.`OrderID`, `o0`.`ProductID`, `o0`.`Discount`, `o0`.`Quantity`, `o0`.`UnitPrice`
+ FROM `Order Details` AS `o0`
+ WHERE `o0`.`OrderID` < 10250
+ UNION ALL
+ SELECT `o1`.`OrderID`, `o1`.`ProductID`, `o1`.`Discount`, `o1`.`Quantity`, `o1`.`UnitPrice`
+ FROM `Order Details` AS `o1`
+ WHERE `o1`.`OrderID` > 11250
+ ) AS `t`
+ WHERE (`t`.`OrderID` = `o`.`OrderID`) AND (`t`.`ProductID` = `o`.`ProductID`))
+""");
+ }
+
+ public override async Task Delete_Intersect(bool async)
+ {
+ await base.Delete_Intersect(async);
+
+ AssertSql(
+"""
+DELETE `o`
+FROM `Order Details` AS `o`
+WHERE EXISTS (
+ SELECT 1
+ FROM (
+ SELECT `o0`.`OrderID`, `o0`.`ProductID`, `o0`.`Discount`, `o0`.`Quantity`, `o0`.`UnitPrice`
+ FROM `Order Details` AS `o0`
+ WHERE `o0`.`OrderID` < 10250
+ INTERSECT
+ SELECT `o1`.`OrderID`, `o1`.`ProductID`, `o1`.`Discount`, `o1`.`Quantity`, `o1`.`UnitPrice`
+ FROM `Order Details` AS `o1`
+ WHERE `o1`.`OrderID` > 11250
+ ) AS `t`
+ WHERE (`t`.`OrderID` = `o`.`OrderID`) AND (`t`.`ProductID` = `o`.`ProductID`))
+""");
+ }
+
+ public override async Task Delete_Except(bool async)
+ {
+ await base.Delete_Except(async);
+
+ AssertSql(
+"""
+DELETE `o`
+FROM `Order Details` AS `o`
+WHERE EXISTS (
+ SELECT 1
+ FROM (
+ SELECT `o0`.`OrderID`, `o0`.`ProductID`, `o0`.`Discount`, `o0`.`Quantity`, `o0`.`UnitPrice`
+ FROM `Order Details` AS `o0`
+ WHERE `o0`.`OrderID` < 10250
+ EXCEPT
+ SELECT `o1`.`OrderID`, `o1`.`ProductID`, `o1`.`Discount`, `o1`.`Quantity`, `o1`.`UnitPrice`
+ FROM `Order Details` AS `o1`
+ WHERE `o1`.`OrderID` > 11250
+ ) AS `t`
+ WHERE (`t`.`OrderID` = `o`.`OrderID`) AND (`t`.`ProductID` = `o`.`ProductID`))
+""");
+ }
+
+ public override async Task Delete_non_entity_projection(bool async)
+ {
+ await base.Delete_non_entity_projection(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_non_entity_projection_2(bool async)
+ {
+ await base.Delete_non_entity_projection_2(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_non_entity_projection_3(bool async)
+ {
+ await base.Delete_non_entity_projection_3(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_FromSql_converted_to_subquery(bool async)
+ {
+ await base.Delete_FromSql_converted_to_subquery(async);
+
+ AssertSql(
+"""
+DELETE `o`
+FROM `Order Details` AS `o`
+WHERE EXISTS (
+ SELECT 1
+ FROM (
+ SELECT `OrderID`, `ProductID`, `UnitPrice`, `Quantity`, `Discount`
+ FROM `Order Details`
+ WHERE `OrderID` < 10300
+ ) AS `m`
+ WHERE (`m`.`OrderID` = `o`.`OrderID`) AND (`m`.`ProductID` = `o`.`ProductID`))
+""");
+ }
+
+ public override async Task Delete_Where_optional_navigation_predicate(bool async)
+ {
+ await base.Delete_Where_optional_navigation_predicate(async);
+
+ AssertSql(
+"""
+DELETE `o`
+FROM `Order Details` AS `o`
+INNER JOIN `Orders` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+LEFT JOIN `Customers` AS `c` ON `o0`.`CustomerID` = `c`.`CustomerID`
+WHERE `c`.`City` IS NOT NULL AND (`c`.`City` LIKE 'Se%')
+""");
+ }
+
+ public override async Task Delete_with_join(bool async)
+ {
+ await base.Delete_with_join(async);
+
+ AssertSql(
+"""
+@__p_1='100'
+@__p_0='0'
+
+DELETE `o`
+FROM `Order Details` AS `o`
+INNER JOIN (
+ SELECT `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`
+ FROM `Orders` AS `o0`
+ WHERE `o0`.`OrderID` < 10300
+ ORDER BY `o0`.`OrderID`
+ LIMIT @__p_1 OFFSET @__p_0
+) AS `t` ON `o`.`OrderID` = `t`.`OrderID`
+""");
+ }
+
+ public override async Task Delete_with_left_join(bool async)
+ {
+ await base.Delete_with_left_join(async);
+
+ AssertSql(
+"""
+@__p_1='100'
+@__p_0='0'
+
+DELETE `o`
+FROM `Order Details` AS `o`
+LEFT JOIN (
+ SELECT `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`
+ FROM `Orders` AS `o0`
+ WHERE `o0`.`OrderID` < 10300
+ ORDER BY `o0`.`OrderID`
+ LIMIT @__p_1 OFFSET @__p_0
+) AS `t` ON `o`.`OrderID` = `t`.`OrderID`
+WHERE `o`.`OrderID` < 10276
+""");
+ }
+
+ public override async Task Delete_with_cross_join(bool async)
+ {
+ await base.Delete_with_cross_join(async);
+
+ AssertSql(
+"""
+DELETE `o`
+FROM `Order Details` AS `o`
+CROSS JOIN (
+ SELECT `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`
+ FROM `Orders` AS `o0`
+ WHERE `o0`.`OrderID` < 10300
+ ORDER BY `o0`.`OrderID`
+ LIMIT 100 OFFSET 0
+) AS `t`
+WHERE `o`.`OrderID` < 10276
+""");
+ }
+
+ public override async Task Delete_with_cross_apply(bool async)
+ {
+ await base.Delete_with_cross_apply(async);
+
+ AssertSql(
+"""
+DELETE `o`
+FROM `Order Details` AS `o`
+JOIN LATERAL (
+ SELECT `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`
+ FROM `Orders` AS `o0`
+ WHERE `o0`.`OrderID` < `o`.`OrderID`
+ ORDER BY `o0`.`OrderID`
+ LIMIT 100 OFFSET 0
+) AS `t` ON TRUE
+WHERE `o`.`OrderID` < 10276
+""");
+ }
+
+ public override async Task Delete_with_outer_apply(bool async)
+ {
+ await base.Delete_with_outer_apply(async);
+
+ AssertSql(
+"""
+DELETE `o`
+FROM `Order Details` AS `o`
+LEFT JOIN LATERAL (
+ SELECT `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`
+ FROM `Orders` AS `o0`
+ WHERE `o0`.`OrderID` < `o`.`OrderID`
+ ORDER BY `o0`.`OrderID`
+ LIMIT 100 OFFSET 0
+) AS `t` ON TRUE
+WHERE `o`.`OrderID` < 10276
+""");
+ }
+
+ public override async Task Update_Where_set_constant_TagWith(bool async)
+ {
+ await base.Update_Where_set_constant_TagWith(async);
+
+ AssertExecuteUpdateSql(
+"""
+-- MyUpdate
+
+UPDATE `Customers` AS `c`
+SET `c`.`ContactName` = 'Updated'
+WHERE `c`.`CustomerID` LIKE 'F%'
+""");
+ }
+
+ public override async Task Update_Where_set_constant(bool async)
+ {
+ await base.Update_Where_set_constant(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Customers` AS `c`
+SET `c`.`ContactName` = 'Updated'
+WHERE `c`.`CustomerID` LIKE 'F%'
+""");
+ }
+
+ public override async Task Update_Where_parameter_set_constant(bool async)
+ {
+ await base.Update_Where_parameter_set_constant(async);
+
+ AssertExecuteUpdateSql(
+ """
+@__customer_0='ALFKI' (Size = 255)
+
+UPDATE `Customers` AS `c`
+SET `c`.`ContactName` = 'Updated'
+WHERE `c`.`CustomerID` = @__customer_0
+""",
+ //
+ """
+@__customer_0='ALFKI' (Size = 255)
+
+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`.`CustomerID` = @__customer_0
+""",
+ //
+ """
+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 FALSE
+""",
+ //
+ """
+UPDATE `Customers` AS `c`
+SET `c`.`ContactName` = 'Updated'
+WHERE FALSE
+""");
+ }
+
+ public override async Task Update_Where_set_parameter(bool async)
+ {
+ await base.Update_Where_set_parameter(async);
+
+ AssertExecuteUpdateSql(
+"""
+@__value_0='Abc' (Size = 4000)
+
+UPDATE `Customers` AS `c`
+SET `c`.`ContactName` = @__value_0
+WHERE `c`.`CustomerID` LIKE 'F%'
+""");
+ }
+
+ public override async Task Update_Where_set_parameter_from_closure_array(bool async)
+ {
+ await base.Update_Where_set_parameter_from_closure_array(async);
+
+ AssertExecuteUpdateSql(
+"""
+@__p_0='Abc' (Size = 4000)
+
+UPDATE `Customers` AS `c`
+SET `c`.`ContactName` = @__p_0
+WHERE `c`.`CustomerID` LIKE 'F%'
+""");
+ }
+
+ public override async Task Update_Where_set_parameter_from_inline_list(bool async)
+ {
+ await base.Update_Where_set_parameter_from_inline_list(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Customers` AS `c`
+SET `c`.`ContactName` = 'Abc'
+WHERE `c`.`CustomerID` LIKE 'F%'
+""");
+ }
+
+ public override async Task Update_Where_set_parameter_from_multilevel_property_access(bool async)
+ {
+ await base.Update_Where_set_parameter_from_multilevel_property_access(async);
+
+ AssertExecuteUpdateSql(
+"""
+@__container_Containee_Property_0='Abc' (Size = 4000)
+
+UPDATE `Customers` AS `c`
+SET `c`.`ContactName` = @__container_Containee_Property_0
+WHERE `c`.`CustomerID` LIKE 'F%'
+""");
+ }
+
+ public override async Task Update_Where_Skip_set_constant(bool async)
+ {
+ await base.Update_Where_Skip_set_constant(async);
+
+ AssertExecuteUpdateSql(
+"""
+@__p_0='4'
+
+UPDATE `Customers` AS `c`
+INNER JOIN (
+ SELECT `c0`.`CustomerID`, `c0`.`Address`, `c0`.`City`, `c0`.`CompanyName`, `c0`.`ContactName`, `c0`.`ContactTitle`, `c0`.`Country`, `c0`.`Fax`, `c0`.`Phone`, `c0`.`PostalCode`, `c0`.`Region`
+ FROM `Customers` AS `c0`
+ WHERE `c0`.`CustomerID` LIKE 'F%'
+ LIMIT 18446744073709551610 OFFSET @__p_0
+) AS `t` ON `c`.`CustomerID` = `t`.`CustomerID`
+SET `c`.`ContactName` = 'Updated'
+""");
+ }
+
+ public override async Task Update_Where_Take_set_constant(bool async)
+ {
+ await AssertUpdate(
+ async,
+ ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")).Take(4),
+ e => e,
+ s => s.SetProperty(c => c.ContactName, "Updated"),
+ rowsAffectedCount: 4,
+ (b, a) => Assert.All(a, c => Assert.Equal("Updated", c.ContactName)));
+
+ AssertExecuteUpdateSql(
+"""
+@__p_0='4'
+
+UPDATE `Customers` AS `c`
+SET `c`.`ContactName` = 'Updated'
+WHERE `c`.`CustomerID` LIKE 'F%'
+LIMIT @__p_0
+""");
+ }
+
+ public override async Task Update_Where_Skip_Take_set_constant(bool async)
+ {
+ await base.Update_Where_Skip_Take_set_constant(async);
+
+ AssertExecuteUpdateSql(
+"""
+@__p_1='4'
+@__p_0='2'
+
+UPDATE `Customers` AS `c`
+INNER JOIN (
+ SELECT `c0`.`CustomerID`, `c0`.`Address`, `c0`.`City`, `c0`.`CompanyName`, `c0`.`ContactName`, `c0`.`ContactTitle`, `c0`.`Country`, `c0`.`Fax`, `c0`.`Phone`, `c0`.`PostalCode`, `c0`.`Region`
+ FROM `Customers` AS `c0`
+ WHERE `c0`.`CustomerID` LIKE 'F%'
+ LIMIT @__p_1 OFFSET @__p_0
+) AS `t` ON `c`.`CustomerID` = `t`.`CustomerID`
+SET `c`.`ContactName` = 'Updated'
+""");
+ }
+
+ public override async Task Update_Where_OrderBy_set_constant(bool async)
+ {
+ await base.Update_Where_OrderBy_set_constant(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Customers` AS `c`
+INNER JOIN (
+ SELECT `c0`.`CustomerID`, `c0`.`Address`, `c0`.`City`, `c0`.`CompanyName`, `c0`.`ContactName`, `c0`.`ContactTitle`, `c0`.`Country`, `c0`.`Fax`, `c0`.`Phone`, `c0`.`PostalCode`, `c0`.`Region`
+ FROM `Customers` AS `c0`
+ WHERE `c0`.`CustomerID` LIKE 'F%'
+) AS `t` ON `c`.`CustomerID` = `t`.`CustomerID`
+SET `c`.`ContactName` = 'Updated'
+""");
+ }
+
+ public override async Task Update_Where_OrderBy_Skip_set_constant(bool async)
+ {
+ await base.Update_Where_OrderBy_Skip_set_constant(async);
+
+ AssertExecuteUpdateSql(
+"""
+@__p_0='4'
+
+UPDATE `Customers` AS `c`
+INNER JOIN (
+ SELECT `c0`.`CustomerID`, `c0`.`Address`, `c0`.`City`, `c0`.`CompanyName`, `c0`.`ContactName`, `c0`.`ContactTitle`, `c0`.`Country`, `c0`.`Fax`, `c0`.`Phone`, `c0`.`PostalCode`, `c0`.`Region`
+ FROM `Customers` AS `c0`
+ WHERE `c0`.`CustomerID` LIKE 'F%'
+ ORDER BY `c0`.`City`
+ LIMIT 18446744073709551610 OFFSET @__p_0
+) AS `t` ON `c`.`CustomerID` = `t`.`CustomerID`
+SET `c`.`ContactName` = 'Updated'
+""");
+ }
+
+ public override async Task Update_Where_OrderBy_Take_set_constant(bool async)
+ {
+ await base.Update_Where_OrderBy_Take_set_constant(async);
+
+ AssertExecuteUpdateSql(
+"""
+@__p_0='4'
+
+UPDATE `Customers` AS `c`
+INNER JOIN (
+ SELECT `c0`.`CustomerID`, `c0`.`Address`, `c0`.`City`, `c0`.`CompanyName`, `c0`.`ContactName`, `c0`.`ContactTitle`, `c0`.`Country`, `c0`.`Fax`, `c0`.`Phone`, `c0`.`PostalCode`, `c0`.`Region`
+ FROM `Customers` AS `c0`
+ WHERE `c0`.`CustomerID` LIKE 'F%'
+ ORDER BY `c0`.`City`
+ LIMIT @__p_0
+) AS `t` ON `c`.`CustomerID` = `t`.`CustomerID`
+SET `c`.`ContactName` = 'Updated'
+""");
+ }
+
+ public override async Task Update_Where_OrderBy_Skip_Take_set_constant(bool async)
+ {
+ await base.Update_Where_OrderBy_Skip_Take_set_constant(async);
+
+ AssertExecuteUpdateSql(
+"""
+@__p_1='4'
+@__p_0='2'
+
+UPDATE `Customers` AS `c`
+INNER JOIN (
+ SELECT `c0`.`CustomerID`, `c0`.`Address`, `c0`.`City`, `c0`.`CompanyName`, `c0`.`ContactName`, `c0`.`ContactTitle`, `c0`.`Country`, `c0`.`Fax`, `c0`.`Phone`, `c0`.`PostalCode`, `c0`.`Region`
+ FROM `Customers` AS `c0`
+ WHERE `c0`.`CustomerID` LIKE 'F%'
+ ORDER BY `c0`.`City`
+ LIMIT @__p_1 OFFSET @__p_0
+) AS `t` ON `c`.`CustomerID` = `t`.`CustomerID`
+SET `c`.`ContactName` = 'Updated'
+""");
+ }
+
+ public override async Task Update_Where_OrderBy_Skip_Take_Skip_Take_set_constant(bool async)
+ {
+ await base.Update_Where_OrderBy_Skip_Take_Skip_Take_set_constant(async);
+
+ AssertExecuteUpdateSql(
+"""
+@__p_1='6'
+@__p_0='2'
+
+UPDATE `Customers` AS `c`
+INNER JOIN (
+ SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`
+ FROM (
+ SELECT `c0`.`CustomerID`, `c0`.`Address`, `c0`.`City`, `c0`.`CompanyName`, `c0`.`ContactName`, `c0`.`ContactTitle`, `c0`.`Country`, `c0`.`Fax`, `c0`.`Phone`, `c0`.`PostalCode`, `c0`.`Region`
+ FROM `Customers` AS `c0`
+ WHERE `c0`.`CustomerID` LIKE 'F%'
+ ORDER BY `c0`.`City`
+ LIMIT @__p_1 OFFSET @__p_0
+ ) AS `t`
+ ORDER BY `t`.`City`
+ LIMIT @__p_0 OFFSET @__p_0
+) AS `t0` ON `c`.`CustomerID` = `t0`.`CustomerID`
+SET `c`.`ContactName` = 'Updated'
+""");
+ }
+
+ public override async Task Update_Where_GroupBy_aggregate_set_constant(bool async)
+ {
+ await base.Update_Where_GroupBy_aggregate_set_constant(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Customers` AS `c`
+SET `c`.`ContactName` = 'Updated'
+WHERE `c`.`CustomerID` = (
+ SELECT `o`.`CustomerID`
+ FROM `Orders` AS `o`
+ GROUP BY `o`.`CustomerID`
+ HAVING COUNT(*) > 11
+ LIMIT 1)
+""");
+ }
+
+ public override async Task Update_Where_GroupBy_First_set_constant(bool async)
+ {
+ await base.Update_Where_GroupBy_First_set_constant(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Customers` AS `c`
+SET `c`.`ContactName` = 'Updated'
+WHERE `c`.`CustomerID` = (
+ SELECT (
+ SELECT `o0`.`CustomerID`
+ FROM `Orders` AS `o0`
+ WHERE (`o`.`CustomerID` = `o0`.`CustomerID`) OR (`o`.`CustomerID` IS NULL AND (`o0`.`CustomerID` IS NULL))
+ LIMIT 1)
+ FROM `Orders` AS `o`
+ GROUP BY `o`.`CustomerID`
+ HAVING COUNT(*) > 11
+ LIMIT 1)
+""");
+ }
+
+ public override async Task Update_Where_GroupBy_First_set_constant_2(bool async)
+ {
+ await base.Update_Where_GroupBy_First_set_constant_2(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ public override async Task Update_Where_GroupBy_First_set_constant_3(bool async)
+ {
+ if (AppConfig.ServerVersion.Type == ServerType.MySql)
+ {
+ // Not supported by MySQL:
+ // Error Code: 1093. You can't specify target table 'c' for update in FROM clause
+ await Assert.ThrowsAsync(
+ () => base.Update_Where_GroupBy_First_set_constant_3(async));
+ }
+ else
+ {
+ // Works as expected in MariaDB.
+ await base.Update_Where_GroupBy_First_set_constant_3(async);
+ }
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Customers` AS `c`
+SET `c`.`ContactName` = 'Updated'
+WHERE EXISTS (
+ SELECT 1
+ FROM `Orders` AS `o`
+ GROUP BY `o`.`CustomerID`
+ HAVING (COUNT(*) > 11) AND ((
+ SELECT `c0`.`CustomerID`
+ FROM `Orders` AS `o0`
+ LEFT JOIN `Customers` AS `c0` ON `o0`.`CustomerID` = `c0`.`CustomerID`
+ WHERE (`o`.`CustomerID` = `o0`.`CustomerID`) OR (`o`.`CustomerID` IS NULL AND (`o0`.`CustomerID` IS NULL))
+ LIMIT 1) = `c`.`CustomerID`))
+""");
+ }
+
+ public override async Task Update_Where_Distinct_set_constant(bool async)
+ {
+ await base.Update_Where_Distinct_set_constant(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Customers` AS `c`
+SET `c`.`ContactName` = 'Updated'
+WHERE `c`.`CustomerID` LIKE 'F%'
+""");
+ }
+
+ public override async Task Update_Where_using_navigation_set_null(bool async)
+ {
+ await base.Update_Where_using_navigation_set_null(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Orders` AS `o`
+LEFT JOIN `Customers` AS `c` ON `o`.`CustomerID` = `c`.`CustomerID`
+SET `o`.`OrderDate` = NULL
+WHERE `c`.`City` = 'Seattle'
+""");
+ }
+
+ public override async Task Update_Where_using_navigation_2_set_constant(bool async)
+ {
+ await base.Update_Where_using_navigation_2_set_constant(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Order Details` AS `o`
+INNER JOIN `Orders` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+LEFT JOIN `Customers` AS `c` ON `o0`.`CustomerID` = `c`.`CustomerID`
+SET `o`.`Quantity` = CAST(1 AS signed)
+WHERE `c`.`City` = 'Seattle'
+""");
+ }
+
+ public override async Task Update_Where_SelectMany_set_null(bool async)
+ {
+ await base.Update_Where_SelectMany_set_null(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Customers` AS `c`
+INNER JOIN `Orders` AS `o` ON `c`.`CustomerID` = `o`.`CustomerID`
+SET `o`.`OrderDate` = NULL
+WHERE `c`.`CustomerID` LIKE 'F%'
+""");
+ }
+
+ public override async Task Update_Where_set_property_plus_constant(bool async)
+ {
+ await base.Update_Where_set_property_plus_constant(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Customers` AS `c`
+SET `c`.`ContactName` = CONCAT(COALESCE(`c`.`ContactName`, ''), 'Abc')
+WHERE `c`.`CustomerID` LIKE 'F%'
+""");
+ }
+
+ public override async Task Update_Where_set_property_plus_parameter(bool async)
+ {
+ await base.Update_Where_set_property_plus_parameter(async);
+
+ AssertExecuteUpdateSql(
+"""
+@__value_0='Abc' (Size = 4000)
+
+UPDATE `Customers` AS `c`
+SET `c`.`ContactName` = CONCAT(COALESCE(`c`.`ContactName`, ''), @__value_0)
+WHERE `c`.`CustomerID` LIKE 'F%'
+""");
+ }
+
+ public override async Task Update_Where_set_property_plus_property(bool async)
+ {
+ await base.Update_Where_set_property_plus_property(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Customers` AS `c`
+SET `c`.`ContactName` = CONCAT(COALESCE(`c`.`ContactName`, ''), `c`.`CustomerID`)
+WHERE `c`.`CustomerID` LIKE 'F%'
+""");
+ }
+
+ public override async Task Update_Where_set_constant_using_ef_property(bool async)
+ {
+ await base.Update_Where_set_constant_using_ef_property(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Customers` AS `c`
+SET `c`.`ContactName` = 'Updated'
+WHERE `c`.`CustomerID` LIKE 'F%'
+""");
+ }
+
+ public override async Task Update_Where_set_null(bool async)
+ {
+ await base.Update_Where_set_null(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Customers` AS `c`
+SET `c`.`ContactName` = NULL
+WHERE `c`.`CustomerID` LIKE 'F%'
+""");
+ }
+
+ public override async Task Update_without_property_to_set_throws(bool async)
+ {
+ await base.Update_without_property_to_set_throws(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ public override async Task Update_with_invalid_lambda_throws(bool async)
+ {
+ await base.Update_with_invalid_lambda_throws(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ public override async Task Update_Where_multiple_set(bool async)
+ {
+ await base.Update_Where_multiple_set(async);
+
+ AssertExecuteUpdateSql(
+"""
+@__value_0='Abc' (Size = 4000)
+
+UPDATE `Customers` AS `c`
+SET `c`.`City` = 'Seattle',
+ `c`.`ContactName` = @__value_0
+WHERE `c`.`CustomerID` LIKE 'F%'
+""");
+ }
+
+ public override async Task Update_with_invalid_lambda_in_set_property_throws(bool async)
+ {
+ await base.Update_with_invalid_lambda_in_set_property_throws(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ public override async Task Update_multiple_entity_throws(bool async)
+ {
+ await base.Update_multiple_entity_throws(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ public override async Task Update_unmapped_property_throws(bool async)
+ {
+ await base.Update_unmapped_property_throws(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ public override async Task Update_Union_set_constant(bool async)
+ {
+ await base.Update_Union_set_constant(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Customers` AS `c`
+INNER JOIN (
+ SELECT `c0`.`CustomerID`, `c0`.`Address`, `c0`.`City`, `c0`.`CompanyName`, `c0`.`ContactName`, `c0`.`ContactTitle`, `c0`.`Country`, `c0`.`Fax`, `c0`.`Phone`, `c0`.`PostalCode`, `c0`.`Region`
+ FROM `Customers` AS `c0`
+ WHERE `c0`.`CustomerID` LIKE 'F%'
+ UNION
+ SELECT `c1`.`CustomerID`, `c1`.`Address`, `c1`.`City`, `c1`.`CompanyName`, `c1`.`ContactName`, `c1`.`ContactTitle`, `c1`.`Country`, `c1`.`Fax`, `c1`.`Phone`, `c1`.`PostalCode`, `c1`.`Region`
+ FROM `Customers` AS `c1`
+ WHERE `c1`.`CustomerID` LIKE 'A%'
+) AS `t` ON `c`.`CustomerID` = `t`.`CustomerID`
+SET `c`.`ContactName` = 'Updated'
+""");
+ }
+
+ public override async Task Update_Concat_set_constant(bool async)
+ {
+ await base.Update_Concat_set_constant(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Customers` AS `c`
+INNER JOIN (
+ SELECT `c0`.`CustomerID`, `c0`.`Address`, `c0`.`City`, `c0`.`CompanyName`, `c0`.`ContactName`, `c0`.`ContactTitle`, `c0`.`Country`, `c0`.`Fax`, `c0`.`Phone`, `c0`.`PostalCode`, `c0`.`Region`
+ FROM `Customers` AS `c0`
+ WHERE `c0`.`CustomerID` LIKE 'F%'
+ UNION ALL
+ SELECT `c1`.`CustomerID`, `c1`.`Address`, `c1`.`City`, `c1`.`CompanyName`, `c1`.`ContactName`, `c1`.`ContactTitle`, `c1`.`Country`, `c1`.`Fax`, `c1`.`Phone`, `c1`.`PostalCode`, `c1`.`Region`
+ FROM `Customers` AS `c1`
+ WHERE `c1`.`CustomerID` LIKE 'A%'
+) AS `t` ON `c`.`CustomerID` = `t`.`CustomerID`
+SET `c`.`ContactName` = 'Updated'
+""");
+ }
+
+ public override async Task Update_Except_set_constant(bool async)
+ {
+ await base.Update_Except_set_constant(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Customers` AS `c`
+INNER JOIN (
+ SELECT `c0`.`CustomerID`, `c0`.`Address`, `c0`.`City`, `c0`.`CompanyName`, `c0`.`ContactName`, `c0`.`ContactTitle`, `c0`.`Country`, `c0`.`Fax`, `c0`.`Phone`, `c0`.`PostalCode`, `c0`.`Region`
+ FROM `Customers` AS `c0`
+ WHERE `c0`.`CustomerID` LIKE 'F%'
+ EXCEPT
+ SELECT `c1`.`CustomerID`, `c1`.`Address`, `c1`.`City`, `c1`.`CompanyName`, `c1`.`ContactName`, `c1`.`ContactTitle`, `c1`.`Country`, `c1`.`Fax`, `c1`.`Phone`, `c1`.`PostalCode`, `c1`.`Region`
+ FROM `Customers` AS `c1`
+ WHERE `c1`.`CustomerID` LIKE 'A%'
+) AS `t` ON `c`.`CustomerID` = `t`.`CustomerID`
+SET `c`.`ContactName` = 'Updated'
+""");
+ }
+
+ public override async Task Update_Intersect_set_constant(bool async)
+ {
+ await base.Update_Intersect_set_constant(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Customers` AS `c`
+INNER JOIN (
+ SELECT `c0`.`CustomerID`, `c0`.`Address`, `c0`.`City`, `c0`.`CompanyName`, `c0`.`ContactName`, `c0`.`ContactTitle`, `c0`.`Country`, `c0`.`Fax`, `c0`.`Phone`, `c0`.`PostalCode`, `c0`.`Region`
+ FROM `Customers` AS `c0`
+ WHERE `c0`.`CustomerID` LIKE 'F%'
+ INTERSECT
+ SELECT `c1`.`CustomerID`, `c1`.`Address`, `c1`.`City`, `c1`.`CompanyName`, `c1`.`ContactName`, `c1`.`ContactTitle`, `c1`.`Country`, `c1`.`Fax`, `c1`.`Phone`, `c1`.`PostalCode`, `c1`.`Region`
+ FROM `Customers` AS `c1`
+ WHERE `c1`.`CustomerID` LIKE 'A%'
+) AS `t` ON `c`.`CustomerID` = `t`.`CustomerID`
+SET `c`.`ContactName` = 'Updated'
+""");
+ }
+
+ public override async Task Update_with_join_set_constant(bool async)
+ {
+ await base.Update_with_join_set_constant(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Customers` AS `c`
+INNER JOIN (
+ SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+ FROM `Orders` AS `o`
+ WHERE `o`.`OrderID` < 10300
+) AS `t` ON `c`.`CustomerID` = `t`.`CustomerID`
+SET `c`.`ContactName` = 'Updated'
+WHERE `c`.`CustomerID` LIKE 'F%'
+""");
+ }
+
+ public override async Task Update_with_left_join_set_constant(bool async)
+ {
+ await base.Update_with_left_join_set_constant(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Customers` AS `c`
+LEFT JOIN (
+ SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+ FROM `Orders` AS `o`
+ WHERE `o`.`OrderID` < 10300
+) AS `t` ON `c`.`CustomerID` = `t`.`CustomerID`
+SET `c`.`ContactName` = 'Updated'
+WHERE `c`.`CustomerID` LIKE 'F%'
+""");
+ }
+
+ public override async Task Update_with_cross_join_set_constant(bool async)
+ {
+ await base.Update_with_cross_join_set_constant(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Customers` AS `c`
+CROSS JOIN (
+ SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+ FROM `Orders` AS `o`
+ WHERE `o`.`OrderID` < 10300
+) AS `t`
+SET `c`.`ContactName` = 'Updated'
+WHERE `c`.`CustomerID` LIKE 'F%'
+""");
+ }
+
+ public override async Task Update_with_cross_apply_set_constant(bool async)
+ {
+ await base.Update_with_cross_apply_set_constant(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Customers` AS `c`
+JOIN LATERAL (
+ SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+ FROM `Orders` AS `o`
+ WHERE (`o`.`OrderID` < 10300) AND (EXTRACT(year FROM `o`.`OrderDate`) < CHAR_LENGTH(`c`.`ContactName`))
+) AS `t` ON TRUE
+SET `c`.`ContactName` = 'Updated'
+WHERE `c`.`CustomerID` LIKE 'F%'
+""");
+ }
+
+ public override async Task Update_with_outer_apply_set_constant(bool async)
+ {
+ await base.Update_with_outer_apply_set_constant(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Customers` AS `c`
+LEFT JOIN LATERAL (
+ SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+ FROM `Orders` AS `o`
+ WHERE (`o`.`OrderID` < 10300) AND (EXTRACT(year FROM `o`.`OrderDate`) < CHAR_LENGTH(`c`.`ContactName`))
+) AS `t` ON TRUE
+SET `c`.`ContactName` = 'Updated'
+WHERE `c`.`CustomerID` LIKE 'F%'
+""");
+ }
+
+ public override async Task Update_with_cross_join_left_join_set_constant(bool async)
+ {
+ await base.Update_with_cross_join_left_join_set_constant(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Customers` AS `c`
+CROSS JOIN (
+ SELECT `c0`.`CustomerID`, `c0`.`Address`, `c0`.`City`, `c0`.`CompanyName`, `c0`.`ContactName`, `c0`.`ContactTitle`, `c0`.`Country`, `c0`.`Fax`, `c0`.`Phone`, `c0`.`PostalCode`, `c0`.`Region`
+ FROM `Customers` AS `c0`
+ WHERE `c0`.`City` IS NOT NULL AND (`c0`.`City` LIKE 'S%')
+) AS `t`
+LEFT JOIN (
+ SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+ FROM `Orders` AS `o`
+ WHERE `o`.`OrderID` < 10300
+) AS `t0` ON `c`.`CustomerID` = `t0`.`CustomerID`
+SET `c`.`ContactName` = 'Updated'
+WHERE `c`.`CustomerID` LIKE 'F%'
+""");
+ }
+
+ public override async Task Update_with_cross_join_cross_apply_set_constant(bool async)
+ {
+ await base.Update_with_cross_join_cross_apply_set_constant(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Customers` AS `c`
+CROSS JOIN (
+ SELECT `c0`.`CustomerID`, `c0`.`Address`, `c0`.`City`, `c0`.`CompanyName`, `c0`.`ContactName`, `c0`.`ContactTitle`, `c0`.`Country`, `c0`.`Fax`, `c0`.`Phone`, `c0`.`PostalCode`, `c0`.`Region`
+ FROM `Customers` AS `c0`
+ WHERE `c0`.`City` IS NOT NULL AND (`c0`.`City` LIKE 'S%')
+) AS `t`
+JOIN LATERAL (
+ SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+ FROM `Orders` AS `o`
+ WHERE (`o`.`OrderID` < 10300) AND (EXTRACT(year FROM `o`.`OrderDate`) < CHAR_LENGTH(`c`.`ContactName`))
+) AS `t0` ON TRUE
+SET `c`.`ContactName` = 'Updated'
+WHERE `c`.`CustomerID` LIKE 'F%'
+""");
+ }
+
+ public override async Task Update_with_cross_join_outer_apply_set_constant(bool async)
+ {
+ await base.Update_with_cross_join_outer_apply_set_constant(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Customers` AS `c`
+CROSS JOIN (
+ SELECT `c0`.`CustomerID`, `c0`.`Address`, `c0`.`City`, `c0`.`CompanyName`, `c0`.`ContactName`, `c0`.`ContactTitle`, `c0`.`Country`, `c0`.`Fax`, `c0`.`Phone`, `c0`.`PostalCode`, `c0`.`Region`
+ FROM `Customers` AS `c0`
+ WHERE `c0`.`City` IS NOT NULL AND (`c0`.`City` LIKE 'S%')
+) AS `t`
+LEFT JOIN LATERAL (
+ SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+ FROM `Orders` AS `o`
+ WHERE (`o`.`OrderID` < 10300) AND (EXTRACT(year FROM `o`.`OrderDate`) < CHAR_LENGTH(`c`.`ContactName`))
+) AS `t0` ON TRUE
+SET `c`.`ContactName` = 'Updated'
+WHERE `c`.`CustomerID` LIKE 'F%'
+""");
+ }
+
+ public override async Task Update_FromSql_set_constant(bool async)
+ {
+ await base.Update_FromSql_set_constant(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ public override async Task Update_Where_SelectMany_subquery_set_null(bool async)
+ {
+ await base.Update_Where_SelectMany_subquery_set_null(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Orders` AS `o`
+INNER JOIN (
+ SELECT `t`.`OrderID`, `t`.`CustomerID`, `t`.`EmployeeID`, `t`.`OrderDate`, `c`.`CustomerID` AS `CustomerID0`
+ FROM `Customers` AS `c`
+ INNER JOIN (
+ SELECT `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`
+ FROM `Orders` AS `o0`
+ WHERE EXTRACT(year FROM `o0`.`OrderDate`) = 1997
+ ) AS `t` ON `c`.`CustomerID` = `t`.`CustomerID`
+ WHERE `c`.`CustomerID` LIKE 'F%'
+) AS `t0` ON `o`.`OrderID` = `t0`.`OrderID`
+SET `o`.`OrderDate` = NULL
+""");
+ }
+
+ public override async Task Update_Where_Join_set_property_from_joined_single_result_table(bool async)
+ {
+ await base.Update_Where_Join_set_property_from_joined_single_result_table(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Customers` AS `c`
+SET `c`.`City` = CAST(EXTRACT(year FROM (
+ SELECT `o`.`OrderDate`
+ FROM `Orders` AS `o`
+ WHERE `c`.`CustomerID` = `o`.`CustomerID`
+ ORDER BY `o`.`OrderDate` DESC
+ LIMIT 1)) AS char)
+WHERE `c`.`CustomerID` LIKE 'F%'
+""");
+ }
+
+ public override async Task Update_Where_Join_set_property_from_joined_table(bool async)
+ {
+ await base.Update_Where_Join_set_property_from_joined_table(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Customers` AS `c`
+CROSS JOIN (
+ SELECT `c0`.`CustomerID`, `c0`.`Address`, `c0`.`City`, `c0`.`CompanyName`, `c0`.`ContactName`, `c0`.`ContactTitle`, `c0`.`Country`, `c0`.`Fax`, `c0`.`Phone`, `c0`.`PostalCode`, `c0`.`Region`
+ FROM `Customers` AS `c0`
+ WHERE `c0`.`CustomerID` = 'ALFKI'
+) AS `t`
+SET `c`.`City` = `t`.`City`
+WHERE `c`.`CustomerID` LIKE 'F%'
+""");
+ }
+
+ public override async Task Update_Where_Join_set_property_from_joined_single_result_scalar(bool async)
+ {
+ await base.Update_Where_Join_set_property_from_joined_single_result_scalar(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Customers` AS `c`
+SET `c`.`City` = CAST(EXTRACT(year FROM (
+ SELECT `o`.`OrderDate`
+ FROM `Orders` AS `o`
+ WHERE `c`.`CustomerID` = `o`.`CustomerID`
+ ORDER BY `o`.`OrderDate` DESC
+ LIMIT 1)) AS char)
+WHERE `c`.`CustomerID` LIKE 'F%'
+""");
+ }
+
+ private void AssertSql(params string[] expected)
+ => Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
+
+ private void AssertExecuteUpdateSql(params string[] expected)
+ => Fixture.TestSqlLoggerFactory.AssertBaseline(expected, forUpdate: true);
+}
diff --git a/test/EFCore.MySql.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesMySqlFixture.cs b/test/EFCore.MySql.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesMySqlFixture.cs
new file mode 100644
index 000000000..cd766f84c
--- /dev/null
+++ b/test/EFCore.MySql.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesMySqlFixture.cs
@@ -0,0 +1,10 @@
+namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests.BulkUpdates;
+
+public class TPCFiltersInheritanceBulkUpdatesMySqlFixture : TPCInheritanceBulkUpdatesMySqlFixture
+{
+ protected override string StoreName
+ => "TPCFiltersInheritanceBulkUpdatesTest";
+
+ protected override bool EnableFilters
+ => true;
+}
diff --git a/test/EFCore.MySql.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesMySqlTest.cs
new file mode 100644
index 000000000..fe57a90ef
--- /dev/null
+++ b/test/EFCore.MySql.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesMySqlTest.cs
@@ -0,0 +1,194 @@
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore.BulkUpdates;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+using Xunit;
+
+namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests.BulkUpdates;
+
+public class TPCFiltersInheritanceBulkUpdatesMySqlTest : TPCFiltersInheritanceBulkUpdatesTestBase<
+ TPCFiltersInheritanceBulkUpdatesMySqlFixture>
+{
+ public TPCFiltersInheritanceBulkUpdatesMySqlTest(TPCFiltersInheritanceBulkUpdatesMySqlFixture fixture)
+ : base(fixture)
+ {
+ ClearLog();
+ }
+
+ [ConditionalFact]
+ public virtual void Check_all_tests_overridden()
+ => TestHelpers.AssertAllMethodsOverridden(GetType());
+
+ public override async Task Delete_where_hierarchy(bool async)
+ {
+ await base.Delete_where_hierarchy(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_where_hierarchy_derived(bool async)
+ {
+ await base.Delete_where_hierarchy_derived(async);
+
+ AssertSql(
+"""
+DELETE `k`
+FROM `Kiwi` AS `k`
+WHERE (`k`.`CountryId` = 1) AND (`k`.`Name` = 'Great spotted kiwi')
+""");
+ }
+
+ public override async Task Delete_where_using_hierarchy(bool async)
+ {
+ await base.Delete_where_using_hierarchy(async);
+
+ AssertSql(
+"""
+DELETE `c`
+FROM `Countries` AS `c`
+WHERE (
+ SELECT COUNT(*)
+ FROM (
+ SELECT `e`.`Id`, `e`.`CountryId`, `e`.`Name`, `e`.`Species`, `e`.`EagleId`, `e`.`IsFlightless`, `e`.`Group`, NULL AS `FoundOn`, 'Eagle' AS `Discriminator`
+ FROM `Eagle` AS `e`
+ UNION ALL
+ SELECT `k`.`Id`, `k`.`CountryId`, `k`.`Name`, `k`.`Species`, `k`.`EagleId`, `k`.`IsFlightless`, NULL AS `Group`, `k`.`FoundOn`, 'Kiwi' AS `Discriminator`
+ FROM `Kiwi` AS `k`
+ ) AS `t`
+ WHERE ((`t`.`CountryId` = 1) AND (`c`.`Id` = `t`.`CountryId`)) AND (`t`.`CountryId` > 0)) > 0
+""");
+ }
+
+ public override async Task Delete_where_using_hierarchy_derived(bool async)
+ {
+ await base.Delete_where_using_hierarchy_derived(async);
+
+ AssertSql(
+"""
+DELETE `c`
+FROM `Countries` AS `c`
+WHERE (
+ SELECT COUNT(*)
+ FROM (
+ SELECT `k`.`Id`, `k`.`CountryId`, `k`.`Name`, `k`.`Species`, `k`.`EagleId`, `k`.`IsFlightless`, NULL AS `Group`, `k`.`FoundOn`, 'Kiwi' AS `Discriminator`
+ FROM `Kiwi` AS `k`
+ ) AS `t`
+ WHERE ((`t`.`CountryId` = 1) AND (`c`.`Id` = `t`.`CountryId`)) AND (`t`.`CountryId` > 0)) > 0
+""");
+ }
+
+ public override async Task Delete_where_keyless_entity_mapped_to_sql_query(bool async)
+ {
+ await base.Delete_where_keyless_entity_mapped_to_sql_query(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_where_hierarchy_subquery(bool async)
+ {
+ await base.Delete_where_hierarchy_subquery(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_GroupBy_Where_Select_First(bool async)
+ {
+ await base.Delete_GroupBy_Where_Select_First(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_GroupBy_Where_Select_First_2(bool async)
+ {
+ await base.Delete_GroupBy_Where_Select_First_2(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_GroupBy_Where_Select_First_3(bool async)
+ {
+ await base.Delete_GroupBy_Where_Select_First_3(async);
+
+ AssertSql();
+ }
+
+ public override async Task Update_where_hierarchy(bool async)
+ {
+ await base.Update_where_hierarchy(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ public override async Task Update_where_hierarchy_subquery(bool async)
+ {
+ await base.Update_where_hierarchy_subquery(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ public override async Task Update_where_hierarchy_derived(bool async)
+ {
+ await base.Update_where_hierarchy_derived(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Kiwi` AS `k`
+SET `k`.`Name` = 'Kiwi'
+WHERE (`k`.`CountryId` = 1) AND (`k`.`Name` = 'Great spotted kiwi')
+""");
+ }
+
+ public override async Task Update_where_using_hierarchy(bool async)
+ {
+ await base.Update_where_using_hierarchy(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Countries` AS `c`
+SET `c`.`Name` = 'Monovia'
+WHERE (
+ SELECT COUNT(*)
+ FROM (
+ SELECT `e`.`Id`, `e`.`CountryId`, `e`.`Name`, `e`.`Species`, `e`.`EagleId`, `e`.`IsFlightless`, `e`.`Group`, NULL AS `FoundOn`, 'Eagle' AS `Discriminator`
+ FROM `Eagle` AS `e`
+ UNION ALL
+ SELECT `k`.`Id`, `k`.`CountryId`, `k`.`Name`, `k`.`Species`, `k`.`EagleId`, `k`.`IsFlightless`, NULL AS `Group`, `k`.`FoundOn`, 'Kiwi' AS `Discriminator`
+ FROM `Kiwi` AS `k`
+ ) AS `t`
+ WHERE ((`t`.`CountryId` = 1) AND (`c`.`Id` = `t`.`CountryId`)) AND (`t`.`CountryId` > 0)) > 0
+""");
+ }
+
+ public override async Task Update_where_using_hierarchy_derived(bool async)
+ {
+ await base.Update_where_using_hierarchy_derived(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Countries` AS `c`
+SET `c`.`Name` = 'Monovia'
+WHERE (
+ SELECT COUNT(*)
+ FROM (
+ SELECT `k`.`Id`, `k`.`CountryId`, `k`.`Name`, `k`.`Species`, `k`.`EagleId`, `k`.`IsFlightless`, NULL AS `Group`, `k`.`FoundOn`, 'Kiwi' AS `Discriminator`
+ FROM `Kiwi` AS `k`
+ ) AS `t`
+ WHERE ((`t`.`CountryId` = 1) AND (`c`.`Id` = `t`.`CountryId`)) AND (`t`.`CountryId` > 0)) > 0
+""");
+ }
+
+ public override async Task Update_where_keyless_entity_mapped_to_sql_query(bool async)
+ {
+ await base.Update_where_keyless_entity_mapped_to_sql_query(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ protected override void ClearLog()
+ => Fixture.TestSqlLoggerFactory.Clear();
+
+ private void AssertSql(params string[] expected)
+ => Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
+
+ private void AssertExecuteUpdateSql(params string[] expected)
+ => Fixture.TestSqlLoggerFactory.AssertBaseline(expected, forUpdate: true);
+}
diff --git a/test/EFCore.MySql.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesMySqlFixture.cs b/test/EFCore.MySql.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesMySqlFixture.cs
new file mode 100644
index 000000000..07d9729be
--- /dev/null
+++ b/test/EFCore.MySql.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesMySqlFixture.cs
@@ -0,0 +1,33 @@
+using System.Linq;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.BulkUpdates;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+using Pomelo.EntityFrameworkCore.MySql.FunctionalTests.TestUtilities;
+
+namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests.BulkUpdates;
+
+public class TPCInheritanceBulkUpdatesMySqlFixture : TPCInheritanceBulkUpdatesFixture
+{
+ protected override ITestStoreFactory TestStoreFactory
+ => MySqlTestStoreFactory.Instance;
+
+ protected override bool UseGeneratedKeys
+ => false;
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
+ {
+ base.OnModelCreating(modelBuilder, context);
+
+ // We currently do not support an official way to set a seed and auto_increment value for auto_increment columns, which is needed
+ // for TPC if the database implementation does not support sequences (which MariaDB does, but we have not fully implemented yet).
+ // We therefore just remove the auto_increment flag from the appropriate entities here, so we do not trigger the related TPC
+ // warning by EF Core.
+ foreach (var tpcPrimaryKey in modelBuilder.Model.GetEntityTypes()
+ .Where(e => e.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy)
+ .Select(e => e.FindPrimaryKey()))
+ {
+ tpcPrimaryKey.Properties.Single().ValueGenerated = ValueGenerated.Never;
+ }
+ }
+}
diff --git a/test/EFCore.MySql.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesMySqlTest.cs
new file mode 100644
index 000000000..92481a93c
--- /dev/null
+++ b/test/EFCore.MySql.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesMySqlTest.cs
@@ -0,0 +1,193 @@
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore.BulkUpdates;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+using Xunit;
+
+namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests.BulkUpdates;
+
+public class TPCInheritanceBulkUpdatesMySqlTest : TPCInheritanceBulkUpdatesTestBase
+{
+ public TPCInheritanceBulkUpdatesMySqlTest(TPCInheritanceBulkUpdatesMySqlFixture fixture)
+ : base(fixture)
+ {
+ ClearLog();
+ }
+
+ [ConditionalFact]
+ public virtual void Check_all_tests_overridden()
+ => TestHelpers.AssertAllMethodsOverridden(GetType());
+
+ public override async Task Delete_where_hierarchy(bool async)
+ {
+ await base.Delete_where_hierarchy(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_where_hierarchy_derived(bool async)
+ {
+ await base.Delete_where_hierarchy_derived(async);
+
+ AssertSql(
+"""
+DELETE `k`
+FROM `Kiwi` AS `k`
+WHERE `k`.`Name` = 'Great spotted kiwi'
+""");
+ }
+
+ public override async Task Delete_where_using_hierarchy(bool async)
+ {
+ await base.Delete_where_using_hierarchy(async);
+
+ AssertSql(
+"""
+DELETE `c`
+FROM `Countries` AS `c`
+WHERE (
+ SELECT COUNT(*)
+ FROM (
+ SELECT `e`.`Id`, `e`.`CountryId`, `e`.`Name`, `e`.`Species`, `e`.`EagleId`, `e`.`IsFlightless`, `e`.`Group`, NULL AS `FoundOn`, 'Eagle' AS `Discriminator`
+ FROM `Eagle` AS `e`
+ UNION ALL
+ SELECT `k`.`Id`, `k`.`CountryId`, `k`.`Name`, `k`.`Species`, `k`.`EagleId`, `k`.`IsFlightless`, NULL AS `Group`, `k`.`FoundOn`, 'Kiwi' AS `Discriminator`
+ FROM `Kiwi` AS `k`
+ ) AS `t`
+ WHERE (`c`.`Id` = `t`.`CountryId`) AND (`t`.`CountryId` > 0)) > 0
+""");
+ }
+
+ public override async Task Delete_where_using_hierarchy_derived(bool async)
+ {
+ await base.Delete_where_using_hierarchy_derived(async);
+
+ AssertSql(
+"""
+DELETE `c`
+FROM `Countries` AS `c`
+WHERE (
+ SELECT COUNT(*)
+ FROM (
+ SELECT `k`.`Id`, `k`.`CountryId`, `k`.`Name`, `k`.`Species`, `k`.`EagleId`, `k`.`IsFlightless`, NULL AS `Group`, `k`.`FoundOn`, 'Kiwi' AS `Discriminator`
+ FROM `Kiwi` AS `k`
+ ) AS `t`
+ WHERE (`c`.`Id` = `t`.`CountryId`) AND (`t`.`CountryId` > 0)) > 0
+""");
+ }
+
+ public override async Task Delete_where_keyless_entity_mapped_to_sql_query(bool async)
+ {
+ await base.Delete_where_keyless_entity_mapped_to_sql_query(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_where_hierarchy_subquery(bool async)
+ {
+ await base.Delete_where_hierarchy_subquery(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_GroupBy_Where_Select_First(bool async)
+ {
+ await base.Delete_GroupBy_Where_Select_First(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_GroupBy_Where_Select_First_2(bool async)
+ {
+ await base.Delete_GroupBy_Where_Select_First_2(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_GroupBy_Where_Select_First_3(bool async)
+ {
+ await base.Delete_GroupBy_Where_Select_First_3(async);
+
+ AssertSql();
+ }
+
+ public override async Task Update_where_hierarchy(bool async)
+ {
+ await base.Update_where_hierarchy(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ public override async Task Update_where_hierarchy_subquery(bool async)
+ {
+ await base.Update_where_hierarchy_subquery(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ public override async Task Update_where_hierarchy_derived(bool async)
+ {
+ await base.Update_where_hierarchy_derived(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Kiwi` AS `k`
+SET `k`.`Name` = 'Kiwi'
+WHERE `k`.`Name` = 'Great spotted kiwi'
+""");
+ }
+
+ public override async Task Update_where_using_hierarchy(bool async)
+ {
+ await base.Update_where_using_hierarchy(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Countries` AS `c`
+SET `c`.`Name` = 'Monovia'
+WHERE (
+ SELECT COUNT(*)
+ FROM (
+ SELECT `e`.`Id`, `e`.`CountryId`, `e`.`Name`, `e`.`Species`, `e`.`EagleId`, `e`.`IsFlightless`, `e`.`Group`, NULL AS `FoundOn`, 'Eagle' AS `Discriminator`
+ FROM `Eagle` AS `e`
+ UNION ALL
+ SELECT `k`.`Id`, `k`.`CountryId`, `k`.`Name`, `k`.`Species`, `k`.`EagleId`, `k`.`IsFlightless`, NULL AS `Group`, `k`.`FoundOn`, 'Kiwi' AS `Discriminator`
+ FROM `Kiwi` AS `k`
+ ) AS `t`
+ WHERE (`c`.`Id` = `t`.`CountryId`) AND (`t`.`CountryId` > 0)) > 0
+""");
+ }
+
+ public override async Task Update_where_using_hierarchy_derived(bool async)
+ {
+ await base.Update_where_using_hierarchy_derived(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Countries` AS `c`
+SET `c`.`Name` = 'Monovia'
+WHERE (
+ SELECT COUNT(*)
+ FROM (
+ SELECT `k`.`Id`, `k`.`CountryId`, `k`.`Name`, `k`.`Species`, `k`.`EagleId`, `k`.`IsFlightless`, NULL AS `Group`, `k`.`FoundOn`, 'Kiwi' AS `Discriminator`
+ FROM `Kiwi` AS `k`
+ ) AS `t`
+ WHERE (`c`.`Id` = `t`.`CountryId`) AND (`t`.`CountryId` > 0)) > 0
+""");
+ }
+
+ public override async Task Update_where_keyless_entity_mapped_to_sql_query(bool async)
+ {
+ await base.Update_where_keyless_entity_mapped_to_sql_query(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ protected override void ClearLog()
+ => Fixture.TestSqlLoggerFactory.Clear();
+
+ private void AssertSql(params string[] expected)
+ => Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
+
+ private void AssertExecuteUpdateSql(params string[] expected)
+ => Fixture.TestSqlLoggerFactory.AssertBaseline(expected, forUpdate: true);
+}
diff --git a/test/EFCore.MySql.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesMySqlFixture.cs b/test/EFCore.MySql.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesMySqlFixture.cs
new file mode 100644
index 000000000..dddec9d11
--- /dev/null
+++ b/test/EFCore.MySql.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesMySqlFixture.cs
@@ -0,0 +1,10 @@
+namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests.BulkUpdates;
+
+public class TPTFiltersInheritanceBulkUpdatesMySqlFixture : TPTInheritanceBulkUpdatesMySqlFixture
+{
+ protected override string StoreName
+ => "TPTFiltersInheritanceBulkUpdatesTest";
+
+ protected override bool EnableFilters
+ => true;
+}
diff --git a/test/EFCore.MySql.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesMySqlTest.cs
new file mode 100644
index 000000000..b63b29e42
--- /dev/null
+++ b/test/EFCore.MySql.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesMySqlTest.cs
@@ -0,0 +1,178 @@
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore.BulkUpdates;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+using Xunit;
+
+namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests.BulkUpdates;
+
+public class TPTFiltersInheritanceBulkUpdatesMySqlTest : TPTFiltersInheritanceBulkUpdatesTestBase<
+ TPTFiltersInheritanceBulkUpdatesMySqlFixture>
+{
+ public TPTFiltersInheritanceBulkUpdatesMySqlTest(TPTFiltersInheritanceBulkUpdatesMySqlFixture fixture)
+ : base(fixture)
+ {
+ ClearLog();
+ }
+
+ [ConditionalFact]
+ public virtual void Check_all_tests_overridden()
+ => TestHelpers.AssertAllMethodsOverridden(GetType());
+
+ public override async Task Delete_where_hierarchy(bool async)
+ {
+ await base.Delete_where_hierarchy(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_where_hierarchy_derived(bool async)
+ {
+ await base.Delete_where_hierarchy_derived(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_where_using_hierarchy(bool async)
+ {
+ await base.Delete_where_using_hierarchy(async);
+
+ AssertSql(
+"""
+DELETE `c`
+FROM `Countries` AS `c`
+WHERE (
+ SELECT COUNT(*)
+ FROM `Animals` AS `a`
+ LEFT JOIN `Birds` AS `b` ON `a`.`Id` = `b`.`Id`
+ LEFT JOIN `Eagle` AS `e` ON `a`.`Id` = `e`.`Id`
+ LEFT JOIN `Kiwi` AS `k` ON `a`.`Id` = `k`.`Id`
+ WHERE ((`a`.`CountryId` = 1) AND (`c`.`Id` = `a`.`CountryId`)) AND (`a`.`CountryId` > 0)) > 0
+""");
+ }
+
+ public override async Task Delete_where_using_hierarchy_derived(bool async)
+ {
+ await base.Delete_where_using_hierarchy_derived(async);
+
+ AssertSql(
+"""
+DELETE `c`
+FROM `Countries` AS `c`
+WHERE (
+ SELECT COUNT(*)
+ FROM `Animals` AS `a`
+ LEFT JOIN `Birds` AS `b` ON `a`.`Id` = `b`.`Id`
+ LEFT JOIN `Eagle` AS `e` ON `a`.`Id` = `e`.`Id`
+ LEFT JOIN `Kiwi` AS `k` ON `a`.`Id` = `k`.`Id`
+ WHERE (((`a`.`CountryId` = 1) AND (`c`.`Id` = `a`.`CountryId`)) AND `k`.`Id` IS NOT NULL) AND (`a`.`CountryId` > 0)) > 0
+""");
+ }
+
+ public override async Task Delete_where_keyless_entity_mapped_to_sql_query(bool async)
+ {
+ await base.Delete_where_keyless_entity_mapped_to_sql_query(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_where_hierarchy_subquery(bool async)
+ {
+ await base.Delete_where_hierarchy_subquery(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_GroupBy_Where_Select_First(bool async)
+ {
+ await base.Delete_GroupBy_Where_Select_First(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_GroupBy_Where_Select_First_2(bool async)
+ {
+ await base.Delete_GroupBy_Where_Select_First_2(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_GroupBy_Where_Select_First_3(bool async)
+ {
+ await base.Delete_GroupBy_Where_Select_First_3(async);
+
+ AssertSql();
+ }
+
+ public override async Task Update_where_hierarchy(bool async)
+ {
+ await base.Update_where_hierarchy(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ public override async Task Update_where_hierarchy_subquery(bool async)
+ {
+ await base.Update_where_hierarchy_subquery(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ public override async Task Update_where_hierarchy_derived(bool async)
+ {
+ await base.Update_where_hierarchy_derived(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ public override async Task Update_where_using_hierarchy(bool async)
+ {
+ await base.Update_where_using_hierarchy(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Countries` AS `c`
+SET `c`.`Name` = 'Monovia'
+WHERE (
+ SELECT COUNT(*)
+ FROM `Animals` AS `a`
+ LEFT JOIN `Birds` AS `b` ON `a`.`Id` = `b`.`Id`
+ LEFT JOIN `Eagle` AS `e` ON `a`.`Id` = `e`.`Id`
+ LEFT JOIN `Kiwi` AS `k` ON `a`.`Id` = `k`.`Id`
+ WHERE ((`a`.`CountryId` = 1) AND (`c`.`Id` = `a`.`CountryId`)) AND (`a`.`CountryId` > 0)) > 0
+""");
+ }
+
+ public override async Task Update_where_using_hierarchy_derived(bool async)
+ {
+ await base.Update_where_using_hierarchy_derived(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Countries` AS `c`
+SET `c`.`Name` = 'Monovia'
+WHERE (
+ SELECT COUNT(*)
+ FROM `Animals` AS `a`
+ LEFT JOIN `Birds` AS `b` ON `a`.`Id` = `b`.`Id`
+ LEFT JOIN `Eagle` AS `e` ON `a`.`Id` = `e`.`Id`
+ LEFT JOIN `Kiwi` AS `k` ON `a`.`Id` = `k`.`Id`
+ WHERE (((`a`.`CountryId` = 1) AND (`c`.`Id` = `a`.`CountryId`)) AND `k`.`Id` IS NOT NULL) AND (`a`.`CountryId` > 0)) > 0
+""");
+ }
+
+ public override async Task Update_where_keyless_entity_mapped_to_sql_query(bool async)
+ {
+ await base.Update_where_keyless_entity_mapped_to_sql_query(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ protected override void ClearLog()
+ => Fixture.TestSqlLoggerFactory.Clear();
+
+ private void AssertSql(params string[] expected)
+ => Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
+
+ private void AssertExecuteUpdateSql(params string[] expected)
+ => Fixture.TestSqlLoggerFactory.AssertBaseline(expected, forUpdate: true);
+}
diff --git a/test/EFCore.MySql.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesMySqlFixture.cs b/test/EFCore.MySql.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesMySqlFixture.cs
new file mode 100644
index 000000000..d7cea5a28
--- /dev/null
+++ b/test/EFCore.MySql.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesMySqlFixture.cs
@@ -0,0 +1,11 @@
+using Microsoft.EntityFrameworkCore.BulkUpdates;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+using Pomelo.EntityFrameworkCore.MySql.FunctionalTests.TestUtilities;
+
+namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests.BulkUpdates;
+
+public class TPTInheritanceBulkUpdatesMySqlFixture : TPTInheritanceBulkUpdatesFixture
+{
+ protected override ITestStoreFactory TestStoreFactory
+ => MySqlTestStoreFactory.Instance;
+}
diff --git a/test/EFCore.MySql.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesMySqlTest.cs
new file mode 100644
index 000000000..d9669a957
--- /dev/null
+++ b/test/EFCore.MySql.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesMySqlTest.cs
@@ -0,0 +1,155 @@
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore.BulkUpdates;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+using Xunit;
+
+namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests.BulkUpdates;
+
+public class TPTInheritanceBulkUpdatesMySqlTest : TPTInheritanceBulkUpdatesTestBase
+{
+ public TPTInheritanceBulkUpdatesMySqlTest(TPTInheritanceBulkUpdatesMySqlFixture fixture)
+ : base(fixture)
+ {
+ ClearLog();
+ }
+
+ [ConditionalFact]
+ public virtual void Check_all_tests_overridden()
+ => TestHelpers.AssertAllMethodsOverridden(GetType());
+
+ public override async Task Delete_where_hierarchy(bool async)
+ {
+ await base.Delete_where_hierarchy(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_where_hierarchy_derived(bool async)
+ {
+ await base.Delete_where_hierarchy_derived(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_where_using_hierarchy(bool async)
+ {
+ await base.Delete_where_using_hierarchy(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_where_using_hierarchy_derived(bool async)
+ {
+ await base.Delete_where_using_hierarchy_derived(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_where_keyless_entity_mapped_to_sql_query(bool async)
+ {
+ await base.Delete_where_keyless_entity_mapped_to_sql_query(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_where_hierarchy_subquery(bool async)
+ {
+ await base.Delete_where_hierarchy_subquery(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_GroupBy_Where_Select_First(bool async)
+ {
+ await base.Delete_GroupBy_Where_Select_First(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_GroupBy_Where_Select_First_2(bool async)
+ {
+ await base.Delete_GroupBy_Where_Select_First_2(async);
+
+ AssertSql();
+ }
+
+ public override async Task Delete_GroupBy_Where_Select_First_3(bool async)
+ {
+ await base.Delete_GroupBy_Where_Select_First_3(async);
+
+ AssertSql();
+ }
+
+ public override async Task Update_where_hierarchy(bool async)
+ {
+ await base.Update_where_hierarchy(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ public override async Task Update_where_hierarchy_subquery(bool async)
+ {
+ await base.Update_where_hierarchy_subquery(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ public override async Task Update_where_hierarchy_derived(bool async)
+ {
+ await base.Update_where_hierarchy_derived(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ public override async Task Update_where_using_hierarchy(bool async)
+ {
+ await base.Update_where_using_hierarchy(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Countries` AS `c`
+SET `c`.`Name` = 'Monovia'
+WHERE (
+ SELECT COUNT(*)
+ FROM `Animals` AS `a`
+ LEFT JOIN `Birds` AS `b` ON `a`.`Id` = `b`.`Id`
+ LEFT JOIN `Eagle` AS `e` ON `a`.`Id` = `e`.`Id`
+ LEFT JOIN `Kiwi` AS `k` ON `a`.`Id` = `k`.`Id`
+ WHERE (`c`.`Id` = `a`.`CountryId`) AND (`a`.`CountryId` > 0)) > 0
+""");
+ }
+
+ public override async Task Update_where_using_hierarchy_derived(bool async)
+ {
+ await base.Update_where_using_hierarchy_derived(async);
+
+ AssertExecuteUpdateSql(
+"""
+UPDATE `Countries` AS `c`
+SET `c`.`Name` = 'Monovia'
+WHERE (
+ SELECT COUNT(*)
+ FROM `Animals` AS `a`
+ LEFT JOIN `Birds` AS `b` ON `a`.`Id` = `b`.`Id`
+ LEFT JOIN `Eagle` AS `e` ON `a`.`Id` = `e`.`Id`
+ LEFT JOIN `Kiwi` AS `k` ON `a`.`Id` = `k`.`Id`
+ WHERE ((`c`.`Id` = `a`.`CountryId`) AND `k`.`Id` IS NOT NULL) AND (`a`.`CountryId` > 0)) > 0
+""");
+ }
+
+ public override async Task Update_where_keyless_entity_mapped_to_sql_query(bool async)
+ {
+ await base.Update_where_keyless_entity_mapped_to_sql_query(async);
+
+ AssertExecuteUpdateSql();
+ }
+
+ protected override void ClearLog()
+ => Fixture.TestSqlLoggerFactory.Clear();
+
+ private void AssertSql(params string[] expected)
+ => Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
+
+ private void AssertExecuteUpdateSql(params string[] expected)
+ => Fixture.TestSqlLoggerFactory.AssertBaseline(expected, forUpdate: true);
+}
diff --git a/test/EFCore.MySql.FunctionalTests/ConnectionInterceptionMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/ConnectionInterceptionMySqlTest.cs
index ba49ee619..143a8a8a4 100644
--- a/test/EFCore.MySql.FunctionalTests/ConnectionInterceptionMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/ConnectionInterceptionMySqlTest.cs
@@ -30,6 +30,9 @@ protected override IServiceCollection InjectInterceptors(
=> base.InjectInterceptors(serviceCollection.AddEntityFrameworkMySql(), injectedInterceptors);
}
+ protected override DbContextOptionsBuilder ConfigureProvider(DbContextOptionsBuilder optionsBuilder)
+ => optionsBuilder.UseMySql(AppConfig.ServerVersion);
+
protected override BadUniverseContext CreateBadUniverse(DbContextOptionsBuilder optionsBuilder)
=> new BadUniverseContext(optionsBuilder.UseMySql(new FakeDbConnection(), AppConfig.ServerVersion).Options);
diff --git a/test/EFCore.MySql.FunctionalTests/DataAnnotationMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/DataAnnotationMySqlTest.cs
index 4be87cde4..569058bee 100644
--- a/test/EFCore.MySql.FunctionalTests/DataAnnotationMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/DataAnnotationMySqlTest.cs
@@ -6,6 +6,7 @@
using Microsoft.EntityFrameworkCore.TestUtilities;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
+using Pomelo.EntityFrameworkCore.MySql.Tests;
using Xunit;
using Xunit.Abstractions;
@@ -24,6 +25,9 @@ public DataAnnotationMySqlTest(DataAnnotationMySqlFixture fixture, ITestOutputHe
protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction)
=> facade.UseTransaction(transaction.GetDbTransaction());
+ protected override TestHelpers TestHelpers
+ => MySqlTestHelpers.Instance;
+
public override IModel Non_public_annotations_are_enabled()
{
var modelBuilder = CreateModelBuilder();
@@ -143,8 +147,27 @@ public override void DatabaseGeneratedAttribute_autogenerates_values_when_set_to
{
base.DatabaseGeneratedAttribute_autogenerates_values_when_set_to_identity();
- AssertSql(
- @"@p0=NULL (Size = 10)
+ if (AppConfig.ServerVersion.Supports.Returning)
+ {
+ AssertSql(
+"""
+@p0=NULL (Size = 10)
+@p1='Third' (Nullable = false) (Size = 4000)
+@p2='00000000-0000-0000-0000-000000000003'
+@p3='Third Additional Name' (Size = 4000)
+@p4='0' (Nullable = true)
+@p5='Third Name' (Size = 4000)
+@p6='0' (Nullable = true)
+
+INSERT INTO `Sample` (`MaxLengthProperty`, `Name`, `RowVersion`, `AdditionalDetails_Name`, `AdditionalDetails_Value`, `Details_Name`, `Details_Value`)
+VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6)
+RETURNING `Unique_No`;
+""");
+ }
+ else
+ {
+ AssertSql(
+ @"@p0=NULL (Size = 10)
@p1='Third' (Nullable = false) (Size = 4000)
@p2='00000000-0000-0000-0000-000000000003'
@p3='Third Additional Name' (Size = 4000)
@@ -157,6 +180,7 @@ public override void DatabaseGeneratedAttribute_autogenerates_values_when_set_to
SELECT `Unique_No`
FROM `Sample`
WHERE ROW_COUNT() = 1 AND `Unique_No` = LAST_INSERT_ID();");
+ }
}
[ConditionalFact]
diff --git a/test/EFCore.MySql.FunctionalTests/EFCore.MySql.FunctionalTests.csproj b/test/EFCore.MySql.FunctionalTests/EFCore.MySql.FunctionalTests.csproj
index 72bb835ee..5c1bb1e68 100644
--- a/test/EFCore.MySql.FunctionalTests/EFCore.MySql.FunctionalTests.csproj
+++ b/test/EFCore.MySql.FunctionalTests/EFCore.MySql.FunctionalTests.csproj
@@ -1,9 +1,10 @@
- $(DefaultNetCoreTargetFramework)
+ $(PomeloTestTargetFramework)
Pomelo.EntityFrameworkCore.MySql.FunctionalTests
Pomelo.EntityFrameworkCore.MySql.FunctionalTests
+ true
$(DefaultItemExcludes);*.trx
@@ -35,28 +36,28 @@
- $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\Debug\$(TargetFramework)\Microsoft.EntityFrameworkCore.dll
+ $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\Debug\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.dll
- $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\Debug\$(TargetFramework)\Microsoft.EntityFrameworkCore.Abstractions.dll
+ $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\Debug\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Abstractions.dll
- $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\Debug\$(TargetFramework)\Microsoft.EntityFrameworkCore.Analyzers.dll
+ $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\Debug\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Analyzers.dll
- $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\Debug\$(TargetFramework)\Microsoft.EntityFrameworkCore.Proxies.dll
+ $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\Debug\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Proxies.dll
- $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\Debug\$(TargetFramework)\Microsoft.EntityFrameworkCore.Relational.dll
+ $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\Debug\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Relational.dll
- $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\Debug\$(TargetFramework)\Microsoft.EntityFrameworkCore.Relational.Specification.Tests.dll
+ $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\Debug\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Relational.Specification.Tests.dll
- $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\Debug\$(TargetFramework)\Microsoft.EntityFrameworkCore.Specification.Tests.dll
+ $(LocalEFCoreRepository)\artifacts\bin\EFCore.Relational.Tests\Debug\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Specification.Tests.dll
- $(LocalEFCoreRepository)\artifacts\bin\EFCore.Design.Tests\Debug\$(TargetFramework)\Microsoft.EntityFrameworkCore.Design.dll
+ $(LocalEFCoreRepository)\artifacts\bin\EFCore.Design.Tests\Debug\$(EfCoreTestTargetFramework)\Microsoft.EntityFrameworkCore.Design.dll
diff --git a/test/EFCore.MySql.FunctionalTests/EntitySplittingMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/EntitySplittingMySqlTest.cs
new file mode 100644
index 000000000..0a499690a
--- /dev/null
+++ b/test/EFCore.MySql.FunctionalTests/EntitySplittingMySqlTest.cs
@@ -0,0 +1,17 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+using Pomelo.EntityFrameworkCore.MySql.FunctionalTests.TestUtilities;
+using Xunit.Abstractions;
+
+namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests;
+
+public class EntitySplittingMySqlTest : EntitySplittingTestBase
+{
+ public EntitySplittingMySqlTest(ITestOutputHelper testOutputHelper)
+ : base(testOutputHelper)
+ {
+ }
+
+ protected override ITestStoreFactory TestStoreFactory
+ => MySqlTestStoreFactory.Instance;
+}
diff --git a/test/EFCore.MySql.FunctionalTests/FindMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/FindMySqlTest.cs
index 4ffaab5a7..a776c1f6e 100644
--- a/test/EFCore.MySql.FunctionalTests/FindMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/FindMySqlTest.cs
@@ -1,26 +1,52 @@
-using System.Threading.Tasks;
-using Pomelo.EntityFrameworkCore.MySql.FunctionalTests.TestUtilities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.TestUtilities;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Pomelo.EntityFrameworkCore.MySql.FunctionalTests.TestUtilities;
+
+namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests;
-namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests
+public abstract class FindMySqlTest : FindTestBase
{
- public class FindMySqlTest : FindTestBase
+ protected FindMySqlTest(FindMySqlFixture fixture)
+ : base(fixture)
+ {
+ fixture.TestSqlLoggerFactory.Clear();
+ }
+
+ public class FindMySqlTestSet : FindMySqlTest
{
- public FindMySqlTest(FindMySqlFixture fixture)
+ public FindMySqlTestSet(FindMySqlFixture fixture)
: base(fixture)
{
}
- protected override TEntity Find(DbContext context, params object[] keyValues)
- => context.Set().Find(keyValues);
+ protected override TestFinder Finder { get; } = new FindViaSetFinder();
+ }
- protected override ValueTask FindAsync(DbContext context, params object[] keyValues)
- => context.Set().FindAsync(keyValues);
+ public class FindMySqlTestContext : FindMySqlTest
+ {
+ public FindMySqlTestContext(FindMySqlFixture fixture)
+ : base(fixture)
+ {
+ }
+
+ protected override TestFinder Finder { get; } = new FindViaContextFinder();
+ }
- public class FindMySqlFixture : FindFixtureBase
+ public class FindMySqlTestNonGeneric : FindMySqlTest
+ {
+ public FindMySqlTestNonGeneric(FindMySqlFixture fixture)
+ : base(fixture)
{
- protected override ITestStoreFactory TestStoreFactory => MySqlTestStoreFactory.Instance;
}
+
+ protected override TestFinder Finder { get; } = new FindViaNonGenericContextFinder();
+ }
+
+ public class FindMySqlFixture : FindFixtureBase
+ {
+ public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ServiceProvider.GetRequiredService();
+ protected override ITestStoreFactory TestStoreFactory => MySqlTestStoreFactory.Instance;
}
}
diff --git a/test/EFCore.MySql.FunctionalTests/GraphUpdatesMySqlClientCascadeTest.cs b/test/EFCore.MySql.FunctionalTests/GraphUpdatesMySqlClientCascadeTest.cs
new file mode 100644
index 000000000..cc94f60dc
--- /dev/null
+++ b/test/EFCore.MySql.FunctionalTests/GraphUpdatesMySqlClientCascadeTest.cs
@@ -0,0 +1,38 @@
+using System.Linq;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage;
+
+namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests;
+
+public class GraphUpdatesMySqlClientCascadeTest : GraphUpdatesMySqlTestBase
+{
+ public GraphUpdatesMySqlClientCascadeTest(MySqlFixture fixture)
+ : base(fixture)
+ {
+ }
+
+ protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction)
+ => facade.UseTransaction(transaction.GetDbTransaction());
+
+ public class MySqlFixture : GraphUpdatesMySqlFixtureBase
+ {
+ public override bool NoStoreCascades
+ => true;
+
+ protected override string StoreName { get; } = "GraphClientCascadeUpdatesTest";
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
+ {
+ base.OnModelCreating(modelBuilder, context);
+
+ foreach (var foreignKey in modelBuilder.Model
+ .GetEntityTypes()
+ .SelectMany(e => e.GetDeclaredForeignKeys())
+ .Where(e => e.DeleteBehavior == DeleteBehavior.Cascade))
+ {
+ foreignKey.DeleteBehavior = DeleteBehavior.ClientCascade;
+ }
+ }
+ }
+}
diff --git a/test/EFCore.MySql.FunctionalTests/GraphUpdatesMySqlClientNoActionTest.cs b/test/EFCore.MySql.FunctionalTests/GraphUpdatesMySqlClientNoActionTest.cs
new file mode 100644
index 000000000..21801edd2
--- /dev/null
+++ b/test/EFCore.MySql.FunctionalTests/GraphUpdatesMySqlClientNoActionTest.cs
@@ -0,0 +1,38 @@
+using System.Linq;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage;
+
+namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests
+{
+ public class GraphUpdatesMySqlClientNoActionTest : GraphUpdatesMySqlTestBase
+ {
+ public GraphUpdatesMySqlClientNoActionTest(MySqlFixture fixture)
+ : base(fixture)
+ {
+ }
+
+ protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction)
+ => facade.UseTransaction(transaction.GetDbTransaction());
+
+ public class MySqlFixture : GraphUpdatesMySqlFixtureBase
+ {
+ public override bool ForceClientNoAction
+ => true;
+
+ protected override string StoreName { get; } = "GraphClientNoActionUpdatesTest";
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
+ {
+ base.OnModelCreating(modelBuilder, context);
+
+ foreach (var foreignKey in modelBuilder.Model
+ .GetEntityTypes()
+ .SelectMany(e => e.GetDeclaredForeignKeys()))
+ {
+ foreignKey.DeleteBehavior = DeleteBehavior.ClientNoAction;
+ }
+ }
+ }
+ }
+}
diff --git a/test/EFCore.MySql.FunctionalTests/GraphUpdatesMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/GraphUpdatesMySqlTest.cs
deleted file mode 100644
index 2475e859c..000000000
--- a/test/EFCore.MySql.FunctionalTests/GraphUpdatesMySqlTest.cs
+++ /dev/null
@@ -1,142 +0,0 @@
-using System.Linq;
-using Pomelo.EntityFrameworkCore.MySql.FunctionalTests.TestUtilities;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Storage;
-using Microsoft.EntityFrameworkCore.TestUtilities;
-
-namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests
-{
- public class GraphUpdatesMySqlTest
- {
- public class ClientCascade : GraphUpdatesMySqlTestBase
- {
- public ClientCascade(MySqlFixture fixture)
- : base(fixture)
- {
- }
-
- protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction)
- => facade.UseTransaction(transaction.GetDbTransaction());
-
- public class MySqlFixture : GraphUpdatesMySqlFixtureBase
- {
- public override bool NoStoreCascades
- => true;
-
- protected override string StoreName { get; } = "GraphClientCascadeUpdatesTest";
-
- protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
- {
- base.OnModelCreating(modelBuilder, context);
-
- foreach (var foreignKey in modelBuilder.Model
- .GetEntityTypes()
- .SelectMany(e => e.GetDeclaredForeignKeys())
- .Where(e => e.DeleteBehavior == DeleteBehavior.Cascade))
- {
- foreignKey.DeleteBehavior = DeleteBehavior.ClientCascade;
- }
- }
- }
- }
-
- public class ClientNoAction : GraphUpdatesMySqlTestBase
- {
- public ClientNoAction(MySqlFixture fixture)
- : base(fixture)
- {
- }
-
- protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction)
- => facade.UseTransaction(transaction.GetDbTransaction());
-
- public class MySqlFixture : GraphUpdatesMySqlFixtureBase
- {
- public override bool ForceClientNoAction
- => true;
-
- protected override string StoreName { get; } = "GraphClientNoActionUpdatesTest";
-
- protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
- {
- base.OnModelCreating(modelBuilder, context);
-
- foreach (var foreignKey in modelBuilder.Model
- .GetEntityTypes()
- .SelectMany(e => e.GetDeclaredForeignKeys()))
- {
- foreignKey.DeleteBehavior = DeleteBehavior.ClientNoAction;
- }
- }
- }
- }
-
- // TODO: UseIdentityColumns()
- // public class Identity : GraphUpdatesMySqlTestBase
- // {
- // public Identity(MySqlFixture fixture)
- // : base(fixture)
- // {
- // }
- //
- // protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction)
- // => facade.UseTransaction(transaction.GetDbTransaction());
- //
- // public class MySqlFixture : GraphUpdatesMySqlFixtureBase
- // {
- // protected override string StoreName { get; } = "GraphIdentityUpdatesTest";
- //
- // protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
- // {
- // modelBuilder.UseIdentityColumns();
- //
- // base.OnModelCreating(modelBuilder, context);
- // }
- // }
- // }
-
- public abstract class GraphUpdatesMySqlTestBase : GraphUpdatesTestBase
- where TFixture : GraphUpdatesMySqlTestBase.GraphUpdatesMySqlFixtureBase, new()
- {
- protected GraphUpdatesMySqlTestBase(TFixture fixture)
- : base(fixture)
- {
- }
-
- protected override IQueryable ModifyQueryRoot(IQueryable query)
- => query.AsSplitQuery();
-
- protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction)
- => facade.UseTransaction(transaction.GetDbTransaction());
-
- public abstract class GraphUpdatesMySqlFixtureBase : GraphUpdatesFixtureBase
- {
- public TestSqlLoggerFactory TestSqlLoggerFactory
- => (TestSqlLoggerFactory)ListLoggerFactory;
-
- protected override ITestStoreFactory TestStoreFactory
- => MySqlTestStoreFactory.Instance;
-
- protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
- {
- base.OnModelCreating(modelBuilder, context);
-
- modelBuilder.Entity(
- b =>
- {
- b.Property(e => e.AccessStateId).ValueGeneratedNever();
- b.HasData(new AccessState {AccessStateId = 1});
- });
-
- modelBuilder.Entity(
- b =>
- {
- b.Property(e => e.IdUserState).HasDefaultValue(1);
- b.HasOne(e => e.UserState).WithMany(e => e.Users).HasForeignKey(e => e.IdUserState);
- });
- }
- }
- }
- }
-}
diff --git a/test/EFCore.MySql.FunctionalTests/GraphUpdatesMySqlTestBase.cs b/test/EFCore.MySql.FunctionalTests/GraphUpdatesMySqlTestBase.cs
new file mode 100644
index 000000000..cc1d34baa
--- /dev/null
+++ b/test/EFCore.MySql.FunctionalTests/GraphUpdatesMySqlTestBase.cs
@@ -0,0 +1,54 @@
+using System.Linq;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+using Pomelo.EntityFrameworkCore.MySql.FunctionalTests.TestUtilities;
+
+namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests;
+
+public abstract class GraphUpdatesMySqlTestBase : GraphUpdatesTestBase
+ where TFixture : GraphUpdatesMySqlTestBase.GraphUpdatesMySqlFixtureBase, new()
+{
+ protected GraphUpdatesMySqlTestBase(TFixture fixture)
+ : base(fixture)
+ {
+ }
+
+ protected override IQueryable ModifyQueryRoot(IQueryable query)
+ => query.AsSplitQuery();
+
+ protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction)
+ => facade.UseTransaction(transaction.GetDbTransaction());
+
+ public abstract class GraphUpdatesMySqlFixtureBase : GraphUpdatesFixtureBase
+ {
+ public TestSqlLoggerFactory TestSqlLoggerFactory
+ => (TestSqlLoggerFactory)ListLoggerFactory;
+
+ protected override ITestStoreFactory TestStoreFactory
+ => MySqlTestStoreFactory.Instance;
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
+ {
+ base.OnModelCreating(modelBuilder, context);
+
+ modelBuilder.Entity(
+ b =>
+ {
+ b.Property(e => e.AccessStateId).ValueGeneratedNever();
+ b.HasData(new AccessState {AccessStateId = 1});
+ });
+
+ modelBuilder.Entity(
+ b =>
+ {
+ b.Property(e => e.IdUserState).HasDefaultValue(1);
+ b.HasOne(e => e.UserState).WithMany(e => e.Users).HasForeignKey(e => e.IdUserState);
+ });
+
+ modelBuilder.Entity().Property("CategoryId").HasDefaultValue(1);
+ modelBuilder.Entity().Property(e => e.CategoryId).HasDefaultValue(2);
+ }
+ }
+}
diff --git a/test/EFCore.MySql.FunctionalTests/LoggingMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/LoggingMySqlTest.cs
index 2871468cc..e504a1d3b 100644
--- a/test/EFCore.MySql.FunctionalTests/LoggingMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/LoggingMySqlTest.cs
@@ -3,7 +3,9 @@
using Pomelo.EntityFrameworkCore.MySql.Infrastructure.Internal;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.TestUtilities;
using Microsoft.Extensions.DependencyInjection;
+using Pomelo.EntityFrameworkCore.MySql.Diagnostics.Internal;
using Pomelo.EntityFrameworkCore.MySql.Tests;
namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests
@@ -22,6 +24,9 @@ protected override DbContextOptionsBuilder CreateOptionsBuilder(
.UseInternalServiceProvider(services.AddEntityFrameworkMySql().BuildServiceProvider(validateScopes: true))
.UseMySql("Database=DummyDatabase", AppConfig.ServerVersion, relationalAction);
+ protected override TestLogger CreateTestLogger()
+ => new TestLogger();
+
protected override string ProviderName => "Pomelo.EntityFrameworkCore.MySql";
protected override string ProviderVersion => typeof(MySqlOptionsExtension).Assembly
diff --git a/test/EFCore.MySql.FunctionalTests/ManyToManyTrackingMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/ManyToManyTrackingMySqlTest.cs
index 9c3a4dd38..117c9f617 100644
--- a/test/EFCore.MySql.FunctionalTests/ManyToManyTrackingMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/ManyToManyTrackingMySqlTest.cs
@@ -10,6 +10,8 @@ public ManyToManyTrackingMySqlTest(ManyToManyTrackingMySqlFixture fixture)
public class ManyToManyTrackingMySqlFixture : ManyToManyTrackingMySqlFixtureBase
{
+ protected override string StoreName
+ => "ManyToManyTrackingMySqlTest";
}
}
}
diff --git a/test/EFCore.MySql.FunctionalTests/ManyToManyTrackingMySqlTestBase.cs b/test/EFCore.MySql.FunctionalTests/ManyToManyTrackingMySqlTestBase.cs
index 299c2c36c..82fd8de7f 100644
--- a/test/EFCore.MySql.FunctionalTests/ManyToManyTrackingMySqlTestBase.cs
+++ b/test/EFCore.MySql.FunctionalTests/ManyToManyTrackingMySqlTestBase.cs
@@ -1,15 +1,22 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
+using System.Data.Common;
+using System.Threading;
+using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.TestModels.ManyToManyModel;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Pomelo.EntityFrameworkCore.MySql.FunctionalTests.TestUtilities;
+using Pomelo.EntityFrameworkCore.MySql.Tests;
namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests
{
- public abstract class ManyToManyTrackingMySqlTestBase : ManyToManyTrackingTestBase
- where TFixture : ManyToManyTrackingTestBase.ManyToManyTrackingFixtureBase
+ public abstract class ManyToManyTrackingMySqlTestBase : ManyToManyTrackingRelationalTestBase
+ where TFixture : ManyToManyTrackingMySqlTestBase.ManyToManyTrackingMySqlFixtureBase
{
protected ManyToManyTrackingMySqlTestBase(TFixture fixture)
: base(fixture)
@@ -19,7 +26,7 @@ protected ManyToManyTrackingMySqlTestBase(TFixture fixture)
protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction)
=> facade.UseTransaction(transaction.GetDbTransaction());
- public class ManyToManyTrackingMySqlFixtureBase : ManyToManyTrackingFixtureBase
+ public class ManyToManyTrackingMySqlFixtureBase : ManyToManyTrackingRelationalFixture
{
protected override ITestStoreFactory TestStoreFactory => MySqlTestStoreFactory.Instance;
@@ -27,10 +34,11 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
{
base.OnModelCreating(modelBuilder, context);
- modelBuilder
+ var propertyBuilder = modelBuilder
.Entity()
- .Property(e => e.Payload)
- .ValueGeneratedOnAdd(); // uses UTC in the original SQL Server implementation
+ .Property(e => e.Payload);
+
+ SetupUtcDefaultValue(propertyBuilder);
modelBuilder
.SharedTypeEntity>("JoinOneToThreePayloadFullShared")
@@ -43,6 +51,82 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
.Property(e => e.Payload)
.HasMaxLength(255) // longtext does not support default values
.HasDefaultValue("Generated");
+
+ propertyBuilder = modelBuilder
+ .Entity()
+ .Property(e => e.Payload);
+
+ SetupUtcDefaultValue(propertyBuilder);
+
+ modelBuilder
+ .SharedTypeEntity>("UnidirectionalJoinOneToThreePayloadFullShared")
+ .IndexerProperty("Payload")
+ .HasMaxLength(255) // longtext does not support default values
+ .HasDefaultValue("Generated");
+
+ modelBuilder
+ .Entity()
+ .Property(e => e.Payload)
+ .HasMaxLength(255) // longtext does not support default values
+ .HasDefaultValue("Generated");
+ }
+
+ private void SetupUtcDefaultValue(PropertyBuilder propertyBuilder)
+ {
+ // The original SQL Server implementation uses GETUTCDATE().
+ if (SupportsDefaultExpressions)
+ {
+ propertyBuilder
+ .HasDefaultValueSql("(UTC_TIMESTAMP())");
+ }
+ else
+ {
+ // This is the same as using .HasDefaultValueSql("CURRENT_TIMESTAMP()");
+ propertyBuilder
+ .ValueGeneratedOnAdd();
+ }
+ }
+
+ protected virtual bool SupportsDefaultExpressions
+ => AppConfig.ServerVersion.Supports.DefaultExpression ||
+ AppConfig.ServerVersion.Supports.AlternativeDefaultExpression;
+
+ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder)
+ {
+ var optionsBuilder = base.AddOptions(builder);
+
+ // To support the Can_insert_many_to_many_self_with_payload_unidirectional test, a UTC_TIMESTAMP would need to be set as the
+ // column default value. Since this depends on default value expression support, we explicitly check for that.
+ // As a workaround for server versions that do not support default value expressions, we will set the default session
+ // timezone to UTC and just use a regular CURRENT_TIMESTAMP for the column default value in question.
+ if (!SupportsDefaultExpressions)
+ {
+ optionsBuilder
+ .AddInterceptors(new SessionInitializingConnectionInterceptor());
+ }
+
+ return optionsBuilder;
+ }
+
+ private class SessionInitializingConnectionInterceptor : DbConnectionInterceptor
+ {
+ public override void ConnectionOpened(DbConnection connection, ConnectionEndEventData eventData)
+ => InitializeSession(connection);
+
+ public override Task ConnectionOpenedAsync(DbConnection connection,
+ ConnectionEndEventData eventData,
+ CancellationToken cancellationToken = new CancellationToken())
+ {
+ InitializeSession(connection);
+ return Task.CompletedTask;
+ }
+
+ private static void InitializeSession(DbConnection connection)
+ {
+ using var command = connection.CreateCommand();
+ command.CommandText = "SET @@session.time_zone = '+00:00'";
+ command.ExecuteNonQuery();
+ }
}
}
}
diff --git a/test/EFCore.MySql.FunctionalTests/MaterializationInterceptionMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/MaterializationInterceptionMySqlTest.cs
new file mode 100644
index 000000000..8861ef23a
--- /dev/null
+++ b/test/EFCore.MySql.FunctionalTests/MaterializationInterceptionMySqlTest.cs
@@ -0,0 +1,41 @@
+using System.Collections.Generic;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Diagnostics;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+using Microsoft.Extensions.DependencyInjection;
+using Pomelo.EntityFrameworkCore.MySql.FunctionalTests.TestUtilities;
+using Pomelo.EntityFrameworkCore.MySql.Storage.Internal;
+using Xunit;
+
+namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests;
+
+public class MaterializationInterceptionMySqlTest : MaterializationInterceptionTestBase,
+ IClassFixture
+{
+ public MaterializationInterceptionMySqlTest(MaterializationInterceptionMySqlFixture fixture)
+ : base(fixture)
+ {
+ }
+
+ public class MaterializationInterceptionMySqlFixture : SingletonInterceptorsFixtureBase
+ {
+ protected override string StoreName
+ => "MaterializationInterception";
+
+ protected override ITestStoreFactory TestStoreFactory
+ => MySqlTestStoreFactory.Instance;
+
+ protected override IServiceCollection InjectInterceptors(
+ IServiceCollection serviceCollection,
+ IEnumerable injectedInterceptors)
+ => base.InjectInterceptors(serviceCollection.AddEntityFrameworkMySql(), injectedInterceptors);
+
+ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder)
+ {
+ new MySqlDbContextOptionsBuilder(base.AddOptions(builder))
+ .ExecutionStrategy(d => new MySqlExecutionStrategy(d));
+ return builder;
+ }
+ }
+}
diff --git a/test/EFCore.MySql.FunctionalTests/MigrationsMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/MigrationsMySqlTest.cs
index 7f4b5a01f..0dfe9f338 100644
--- a/test/EFCore.MySql.FunctionalTests/MigrationsMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/MigrationsMySqlTest.cs
@@ -4,7 +4,10 @@
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Metadata.Conventions;
using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Migrations.Design;
using Microsoft.EntityFrameworkCore.Scaffolding;
using Microsoft.EntityFrameworkCore.Scaffolding.Metadata;
using Microsoft.EntityFrameworkCore.TestUtilities;
@@ -267,9 +270,15 @@ await Test(
}
[ConditionalTheory(Skip = "TODO")]
- public override Task Add_primary_key()
+ public override Task Add_primary_key_int()
{
- return base.Add_primary_key();
+ return base.Add_primary_key_int();
+ }
+
+ [ConditionalTheory(Skip = "TODO")]
+ public override async Task Add_primary_key_string()
+ {
+ await base.Add_primary_key_string();
}
[ConditionalTheory(Skip = "TODO")]
@@ -314,13 +323,42 @@ public override Task Alter_column_set_collation()
return base.Alter_column_set_collation();
}
- [ConditionalTheory(Skip = "TODO")]
- public override Task Alter_sequence_all_settings()
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.Sequences))]
+ public override async Task Alter_sequence_all_settings()
{
- return base.Alter_sequence_all_settings();
+ await Test(
+ builder => builder.HasSequence("foo"),
+ builder => { },
+ builder => builder.HasSequence("foo")
+ .StartsAt(-3)
+ .IncrementsBy(2)
+ .HasMin(-5)
+ .HasMax(10)
+ .IsCyclic(),
+ model =>
+ {
+ var sequence = Assert.Single(model.Sequences);
+
+ // Assert.Equal(-3, sequence.StartValue);
+ Assert.Equal(1, sequence.StartValue); // Restarting doesn't change the scaffolded start value
+
+ Assert.Equal(2, sequence.IncrementBy);
+ Assert.Equal(-5, sequence.MinValue);
+ Assert.Equal(10, sequence.MaxValue);
+ Assert.True(sequence.IsCyclic);
+ });
+
+ AssertSql(
+ """
+ALTER SEQUENCE `foo` INCREMENT BY 2 MINVALUE -5 MAXVALUE 10 CYCLE;
+""",
+ //
+ """
+ALTER SEQUENCE `foo` RESTART WITH -3;
+""");
}
- [ConditionalTheory(Skip = "TODO")]
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.Sequences))]
public override Task Alter_sequence_increment_by()
{
return base.Alter_sequence_increment_by();
@@ -346,16 +384,54 @@ public override Task Create_schema()
return base.Create_schema();
}
- [ConditionalTheory(Skip = "TODO")]
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.Sequences))]
public override Task Create_sequence()
{
return base.Create_sequence();
}
- [ConditionalTheory(Skip = "TODO")]
- public override Task Create_sequence_all_settings()
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.Sequences))]
+ public override Task Create_sequence_long()
{
- return base.Create_sequence_all_settings();
+ return base.Create_sequence_long();
+ }
+
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.Sequences))]
+ public override Task Create_sequence_short()
+ {
+ return base.Create_sequence_short();
+ }
+
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.Sequences))]
+ public override async Task Create_sequence_all_settings()
+ {
+ await Test(
+ builder => { },
+ builder => builder.HasSequence("TestSequence", "dbo2")
+ .StartsAt(3)
+ .IncrementsBy(2)
+ .HasMin(2)
+ .HasMax(916)
+ .IsCyclic(),
+ model =>
+ {
+ var sequence = Assert.Single(model.Sequences);
+
+ // Assert.Equal("TestSequence", sequence.Name);
+ // Assert.Equal("dbo2", sequence.Schema);
+ Assert.Equal("dbo2_TestSequence", sequence.Name);
+
+ Assert.Equal(3, sequence.StartValue);
+ Assert.Equal(2, sequence.IncrementBy);
+ Assert.Equal(2, sequence.MinValue);
+ Assert.Equal(916, sequence.MaxValue);
+ Assert.True(sequence.IsCyclic);
+ });
+
+ AssertSql(
+"""
+CREATE SEQUENCE `dbo2_TestSequence` START WITH 3 INCREMENT BY 2 MINVALUE 2 MAXVALUE 916 CYCLE;
+""");
}
[ConditionalTheory(Skip = "TODO")]
@@ -370,11 +446,12 @@ public override async Task Create_table_with_multiline_comments()
AssertSql(
@"CREATE TABLE `People` (
- `Id` int NOT NULL,
+ `Id` int NOT NULL AUTO_INCREMENT,
`Name` longtext CHARACTER SET utf8mb4 NULL COMMENT 'This is a multi-line
column comment.
More information can
-be found in the docs.'
+be found in the docs.',
+ CONSTRAINT `PK_People` PRIMARY KEY (`Id`)
) CHARACTER SET=utf8mb4 COMMENT='This is a multi-line
table comment.
More information can
@@ -387,6 +464,35 @@ public override Task Create_unique_index_with_filter()
return base.Create_unique_index_with_filter();
}
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.DescendingIndexes))]
+ public override async Task Create_index_descending()
+ {
+ await base.Create_index_descending();
+
+ AssertSql(
+ @"CREATE INDEX `IX_People_X` ON `People` (`X` DESC);");
+ }
+
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.DescendingIndexes))]
+ public override async Task Create_index_descending_mixed()
+ {
+ await base.Create_index_descending_mixed();
+
+ AssertSql(
+ @"CREATE INDEX `IX_People_X_Y_Z` ON `People` (`X`, `Y` DESC, `Z`);");
+ }
+
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.DescendingIndexes))]
+ public override async Task Alter_index_change_sort_order()
+ {
+ await base.Alter_index_change_sort_order();
+
+ AssertSql(
+ @"ALTER TABLE `People` DROP INDEX `IX_People_X_Y_Z`;",
+ //
+ @"CREATE INDEX `IX_People_X_Y_Z` ON `People` (`X`, `Y` DESC, `Z`);");
+ }
+
[ConditionalTheory(Skip = "TODO: Syntax issue in MySQL 7 only.")]
public override Task Drop_check_constraint()
{
@@ -400,21 +506,41 @@ public override Task Drop_column_primary_key()
}
[ConditionalTheory(Skip = "TODO")]
- public override Task Drop_primary_key()
+ public override Task Drop_primary_key_int()
{
- return base.Drop_primary_key();
+ return base.Drop_primary_key_int();
}
[ConditionalTheory(Skip = "TODO")]
+ public override async Task Drop_primary_key_string()
+ {
+ await base.Drop_primary_key_string();
+ }
+
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.Sequences))]
public override Task Drop_sequence()
{
return base.Drop_sequence();
}
- [ConditionalTheory(Skip = "TODO")]
- public override Task Move_sequence()
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.Sequences))]
+ public override async Task Move_sequence()
{
- return base.Move_sequence();
+ await Test(
+ builder => builder.HasSequence("TestSequenceMove"),
+ builder => builder.HasSequence("TestSequenceMove", "TestSequenceSchema"),
+ model =>
+ {
+ var sequence = Assert.Single(model.Sequences);
+ // Assert.Equal("TestSequenceSchema", sequence.Schema);
+ // Assert.Equal("TestSequence", sequence.Name);
+ Assert.Equal("TestSequenceSchema_TestSequenceMove", sequence.Name);
+ });
+
+ AssertSql(
+"""
+ALTER TABLE `TestSequenceMove` RENAME `TestSequenceSchema_TestSequenceMove`;
+""");
}
[ConditionalTheory(Skip = "TODO")]
@@ -423,10 +549,15 @@ public override Task Move_table()
return base.Move_table();
}
- [ConditionalTheory(Skip = "TODO")]
- public override Task Rename_sequence()
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.Sequences))]
+ public override async Task Rename_sequence()
{
- return base.Rename_sequence();
+ await base.Rename_sequence();
+
+ AssertSql(
+"""
+ALTER TABLE `TestSequence` RENAME `testsequence`;
+""");
}
[ConditionalTheory(Skip = "TODO")]
@@ -535,9 +666,10 @@ await Test(
});
AssertSql(
- $@"CREATE TABLE `IceCream` (
- `IceCreamId` int NOT NULL,
- `Name` NVARCHAR(45) NULL
+ @"CREATE TABLE `IceCream` (
+ `IceCreamId` int NOT NULL AUTO_INCREMENT,
+ `Name` NVARCHAR(45) NULL,
+ CONSTRAINT `PK_IceCream` PRIMARY KEY (`IceCreamId`)
) CHARACTER SET=utf8mb4;");
}
@@ -564,7 +696,8 @@ await Test(
$@"ALTER DATABASE COLLATE {DefaultCollation};",
//
$@"CREATE TABLE `IceCream` (
- `IceCreamId` char(36) COLLATE ascii_general_ci NOT NULL
+ `IceCreamId` char(36) COLLATE ascii_general_ci NOT NULL,
+ CONSTRAINT `PK_IceCream` PRIMARY KEY (`IceCreamId`)
) COLLATE={DefaultCollation};");
}
@@ -592,7 +725,8 @@ await Test(
$@"ALTER DATABASE COLLATE {DefaultCollation};",
//
$@"CREATE TABLE `IceCream` (
- `IceCreamId` char(36) COLLATE {NonDefaultCollation} NOT NULL
+ `IceCreamId` char(36) COLLATE {NonDefaultCollation} NOT NULL,
+ CONSTRAINT `PK_IceCream` PRIMARY KEY (`IceCreamId`)
) COLLATE={DefaultCollation};");
}
@@ -620,7 +754,8 @@ await Test(
$@"ALTER DATABASE COLLATE {DefaultCollation};",
//
$@"CREATE TABLE `IceCream` (
- `IceCreamId` char(36) COLLATE {NonDefaultCollation} NOT NULL
+ `IceCreamId` char(36) COLLATE {NonDefaultCollation} NOT NULL,
+ CONSTRAINT `PK_IceCream` PRIMARY KEY (`IceCreamId`)
) COLLATE={DefaultCollation};");
}
@@ -648,7 +783,8 @@ await Test(
$@"ALTER DATABASE COLLATE {DefaultCollation};",
//
$@"CREATE TABLE `IceCream` (
- `IceCreamId` char(36) NOT NULL
+ `IceCreamId` char(36) NOT NULL,
+ CONSTRAINT `PK_IceCream` PRIMARY KEY (`IceCreamId`)
) COLLATE={DefaultCollation};");
}
@@ -771,6 +907,17 @@ await Test(
result => { });
AssertSql(
+ @"set @__pomelo_TableCharset = (
+ SELECT `ccsa`.`CHARACTER_SET_NAME` as `TABLE_CHARACTER_SET`
+ FROM `INFORMATION_SCHEMA`.`TABLES` as `t`
+ LEFT JOIN `INFORMATION_SCHEMA`.`COLLATION_CHARACTER_SET_APPLICABILITY` as `ccsa` ON `ccsa`.`COLLATION_NAME` = `t`.`TABLE_COLLATION`
+ WHERE `TABLE_SCHEMA` = SCHEMA() AND `TABLE_NAME` = 'IceCream' AND `TABLE_TYPE` IN ('BASE TABLE', 'VIEW'));
+
+SET @__pomelo_SqlExpr = CONCAT('ALTER TABLE `IceCream` CHARACTER SET = ', @__pomelo_TableCharset, ';');
+PREPARE __pomelo_SqlExprExecute FROM @__pomelo_SqlExpr;
+EXECUTE __pomelo_SqlExprExecute;
+DEALLOCATE PREPARE __pomelo_SqlExprExecute;",
+ //
$@"ALTER TABLE `IceCream` MODIFY COLUMN `Name` longtext COLLATE {NonDefaultCollation} NULL;",
//
$@"ALTER TABLE `IceCream` MODIFY COLUMN `Brand` longtext COLLATE {NonDefaultCollation2} NULL;");
@@ -811,8 +958,6 @@ await Test(
result => { });
AssertSql(
- $@"ALTER TABLE `IceCream` COLLATE={DefaultCollation};",
- //
$@"ALTER TABLE `IceCream` MODIFY COLUMN `Name` longtext COLLATE {NonDefaultCollation} NULL;",
//
$@"ALTER TABLE `IceCream` MODIFY COLUMN `Brand` longtext COLLATE {NonDefaultCollation2} NULL;");
@@ -907,9 +1052,10 @@ await Test(
$@"ALTER DATABASE COLLATE {DefaultCollation};",
//
$@"CREATE TABLE `IceCream` (
+ `IceCreamId` int NOT NULL AUTO_INCREMENT,
`Brand` longtext CHARACTER SET {NonDefaultCharSet} NULL,
- `IceCreamId` int NOT NULL,
- `Name` longtext COLLATE {DefaultCollation} NULL
+ `Name` longtext COLLATE {DefaultCollation} NULL,
+ CONSTRAINT `PK_IceCream` PRIMARY KEY (`IceCreamId`)
) COLLATE={DefaultCollation};");
}
@@ -946,9 +1092,10 @@ await Test(
$@"ALTER DATABASE CHARACTER SET {NonDefaultCharSet};",
//
$@"CREATE TABLE `IceCream` (
+ `IceCreamId` int NOT NULL AUTO_INCREMENT,
`Brand` longtext COLLATE {NonDefaultCollation2} NULL,
- `IceCreamId` int NOT NULL,
- `Name` longtext CHARACTER SET {NonDefaultCharSet} NULL
+ `Name` longtext CHARACTER SET {NonDefaultCharSet} NULL,
+ CONSTRAINT `PK_IceCream` PRIMARY KEY (`IceCreamId`)
) CHARACTER SET={NonDefaultCharSet};");
}
@@ -979,8 +1126,9 @@ await Test(
AssertSql(
$@"CREATE TABLE `IceCream` (
- `IceCreamId` int NOT NULL,
- `Name` longtext CHARACTER SET {NonDefaultCharSet} NULL
+ `IceCreamId` int NOT NULL AUTO_INCREMENT,
+ `Name` longtext CHARACTER SET {NonDefaultCharSet} NULL,
+ CONSTRAINT `PK_IceCream` PRIMARY KEY (`IceCreamId`)
) CHARACTER SET=utf8mb4;");
}
@@ -1125,7 +1273,7 @@ await Test(
//
@"ALTER TABLE `Foo` DROP KEY `AK_Foo_FooAK`;",
//
- @"ALTER TABLE `Foo` ADD CONSTRAINT `FK_Foo_Bar_BarFK` FOREIGN KEY (`BarFK`) REFERENCES `Bar` (`BarPK`);");
+ @"ALTER TABLE `Foo` ADD CONSTRAINT `FK_Foo_Bar_BarFK` FOREIGN KEY (`BarFK`) REFERENCES `Bar` (`BarPK`) ON DELETE CASCADE;");
}
public override async Task Add_foreign_key()
@@ -1133,9 +1281,22 @@ public override async Task Add_foreign_key()
await base.Add_foreign_key();
AssertSql(
- @"ALTER TABLE `Orders` ADD CONSTRAINT `FK_Orders_Customers_CustomerId` FOREIGN KEY (`CustomerId`) REFERENCES `Customers` (`Id`);");
+ @"CREATE INDEX `IX_Orders_CustomerId` ON `Orders` (`CustomerId`);",
+ //
+ @"ALTER TABLE `Orders` ADD CONSTRAINT `FK_Orders_Customers_CustomerId` FOREIGN KEY (`CustomerId`) REFERENCES `Customers` (`Id`) ON DELETE CASCADE;");
}
+ public override Task Rename_table()
+ => Test(
+ builder => builder.Entity("People").Property("Id"),
+ builder => builder.Entity("People").ToTable("Persons").Property("Id"),
+ model =>
+ {
+ var table = Assert.Single(model.Tables);
+ Assert.Equal("Persons", table.Name);
+ },
+ withConventions: false);
+
protected virtual string DefaultCollation => ((MySqlTestStore)Fixture.TestStore).DatabaseCollation;
protected override string NonDefaultCollation
@@ -1157,22 +1318,32 @@ protected virtual Task Test(
Action buildSourceAction,
Action buildTargetAction,
Action migrationBuilderAction,
- Action asserter)
+ Action asserter,
+ bool withConventions = true)
{
var services = TestHelpers.CreateContextServices();
+ var modelRuntimeInitializer = services.GetRequiredService();
- // Build the source and target models. Add current/latest product version if one wasn't set.
- var sourceModelBuilder = CreateConventionlessModelBuilder();
+ // Build the source model, possibly with conventions
+ var sourceModelBuilder = CreateModelBuilder(withConventions);
buildCommonAction(sourceModelBuilder);
buildSourceAction(sourceModelBuilder);
- var sourceModel = services.GetRequiredService()
- .Initialize(sourceModelBuilder.FinalizeModel(), designTime: true, validationLogger: null);
-
- var targetModelBuilder = CreateConventionlessModelBuilder();
+ var preSnapshotSourceModel = modelRuntimeInitializer.Initialize(
+ (IModel)sourceModelBuilder.Model, designTime: true, validationLogger: null);
+
+ // Round-trip the source model through a snapshot, compiling it and then extracting it back again.
+ // This simulates the real-world migration flow and can expose errors in snapshot generation
+ var migrationsCodeGenerator = Fixture.TestHelpers.CreateDesignServiceProvider().GetRequiredService();
+ var sourceModelSnapshot = migrationsCodeGenerator.GenerateSnapshot(
+ modelSnapshotNamespace: null, typeof(DbContext), "MigrationsTestSnapshot", preSnapshotSourceModel);
+ var sourceModel = BuildModelFromSnapshotSource(sourceModelSnapshot);
+
+ // Build the target model, possibly with conventions
+ var targetModelBuilder = CreateModelBuilder(withConventions);
buildCommonAction(targetModelBuilder);
buildTargetAction(targetModelBuilder);
- var targetModel = services.GetRequiredService()
- .Initialize(targetModelBuilder.FinalizeModel(), designTime: true, validationLogger: null);
+ var targetModel = modelRuntimeInitializer.Initialize(
+ (IModel)targetModelBuilder.Model, designTime: true, validationLogger: null);
var migrationBuilder = new MigrationBuilder(null);
migrationBuilderAction(migrationBuilder);
@@ -1180,13 +1351,16 @@ protected virtual Task Test(
return Test(sourceModel, targetModel, migrationBuilder.Operations, asserter);
}
+ private ModelBuilder CreateModelBuilder(bool withConventions)
+ => withConventions ? Fixture.TestHelpers.CreateConventionBuilder() : new ModelBuilder(new ConventionSet());
+
public class MigrationsMySqlFixture : MigrationsFixtureBase
{
protected override string StoreName
=> nameof(MigrationsMySqlTest);
protected override ITestStoreFactory TestStoreFactory => MySqlTestStoreFactory.Instance;
- public override TestHelpers TestHelpers => MySqlTestHelpers.Instance;
+ public override RelationalTestHelpers TestHelpers => MySqlTestHelpers.Instance;
public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder)
{
diff --git a/test/EFCore.MySql.FunctionalTests/MySqlComplianceTest.cs b/test/EFCore.MySql.FunctionalTests/MySqlComplianceTest.cs
index 8f6e3e696..8d29abdc8 100644
--- a/test/EFCore.MySql.FunctionalTests/MySqlComplianceTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/MySqlComplianceTest.cs
@@ -3,6 +3,7 @@
using System.Reflection;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query;
+using Microsoft.EntityFrameworkCore.Update;
namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests
{
@@ -18,7 +19,12 @@ public class MySqlComplianceTest : RelationalComplianceTestBase
// TODO: Reenable LoggingMySqlTest once its issue has been fixed in EF Core upstream.
typeof(LoggingTestBase),
- typeof(LoggingRelationalTestBase<,>)
+ typeof(LoggingRelationalTestBase<,>),
+
+ // We have our own JSON support for now
+ typeof(JsonUpdateTestBase<>),
+ typeof(JsonQueryTestBase<>),
+ typeof(JsonQueryAdHocTestBase),
};
protected override Assembly TargetAssembly { get; } = typeof(MySqlComplianceTest).Assembly;
diff --git a/test/EFCore.MySql.FunctionalTests/MySqlMigrationsSqlGeneratorTest.cs b/test/EFCore.MySql.FunctionalTests/MySqlMigrationsSqlGeneratorTest.cs
index d8af3638d..eb91eced8 100644
--- a/test/EFCore.MySql.FunctionalTests/MySqlMigrationsSqlGeneratorTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/MySqlMigrationsSqlGeneratorTest.cs
@@ -123,21 +123,14 @@ public override void InsertDataOperation_all_args_spatial()
AssertSql(
@"INSERT INTO `People` (`Id`, `Full Name`, `Geometry`)
-VALUES (0, NULL, NULL);
-INSERT INTO `People` (`Id`, `Full Name`, `Geometry`)
-VALUES (1, 'Daenerys Targaryen', NULL);
-INSERT INTO `People` (`Id`, `Full Name`, `Geometry`)
-VALUES (2, 'John Snow', NULL);
-INSERT INTO `People` (`Id`, `Full Name`, `Geometry`)
-VALUES (3, 'Arya Stark', NULL);
-INSERT INTO `People` (`Id`, `Full Name`, `Geometry`)
-VALUES (4, 'Harry Strickland', NULL);
-INSERT INTO `People` (`Id`, `Full Name`, `Geometry`)
-VALUES (5, 'The Imp', NULL);
-INSERT INTO `People` (`Id`, `Full Name`, `Geometry`)
-VALUES (6, 'The Kingslayer', NULL);
-INSERT INTO `People` (`Id`, `Full Name`, `Geometry`)
-VALUES (7, 'Aemon Targaryen', X'E61000000107000000080000000102000000040000009A9999999999F13F9A999999999901409A999999999901409A999999999901409A999999999901409A9999999999F13F6666666666661C40CDCCCCCCCCCC1C400102000000040000006666666666661C40CDCCCCCCCCCC1C403333333333333440333333333333344033333333333334409A9999999999F13F6666666666865140CDCCCCCCCC8C514001040000000300000001010000009A9999999999F13F9A9999999999014001010000009A999999999901409A9999999999014001010000009A999999999901409A9999999999F13F010300000001000000040000009A9999999999F13F9A999999999901409A999999999901409A999999999901409A999999999901409A9999999999F13F9A9999999999F13F9A99999999990140010300000001000000040000003333333333332440333333333333344033333333333334403333333333333440333333333333344033333333333324403333333333332440333333333333344001010000009A9999999999F13F9A999999999901400105000000020000000102000000040000009A9999999999F13F9A999999999901409A999999999901409A999999999901409A999999999901409A9999999999F13F6666666666661C40CDCCCCCCCCCC1C400102000000040000006666666666661C40CDCCCCCCCCCC1C403333333333333440333333333333344033333333333334409A9999999999F13F6666666666865140CDCCCCCCCC8C51400106000000020000000103000000010000000400000033333333333324403333333333333440333333333333344033333333333334403333333333333440333333333333244033333333333324403333333333333440010300000001000000040000009A9999999999F13F9A999999999901409A999999999901409A999999999901409A999999999901409A9999999999F13F9A9999999999F13F9A99999999990140');");
+VALUES (0, NULL, NULL),
+(1, 'Daenerys Targaryen', NULL),
+(2, 'John Snow', NULL),
+(3, 'Arya Stark', NULL),
+(4, 'Harry Strickland', NULL),
+(5, 'The Imp', NULL),
+(6, 'The Kingslayer', NULL),
+(7, 'Aemon Targaryen', X'E61000000107000000080000000102000000040000009A9999999999F13F9A999999999901409A999999999901409A999999999901409A999999999901409A9999999999F13F6666666666661C40CDCCCCCCCCCC1C400102000000040000006666666666661C40CDCCCCCCCCCC1C403333333333333440333333333333344033333333333334409A9999999999F13F6666666666865140CDCCCCCCCC8C514001040000000300000001010000009A9999999999F13F9A9999999999014001010000009A999999999901409A9999999999014001010000009A999999999901409A9999999999F13F010300000001000000040000009A9999999999F13F9A999999999901409A999999999901409A999999999901409A999999999901409A9999999999F13F9A9999999999F13F9A99999999990140010300000001000000040000003333333333332440333333333333344033333333333334403333333333333440333333333333344033333333333324403333333333332440333333333333344001010000009A9999999999F13F9A999999999901400105000000020000000102000000040000009A9999999999F13F9A999999999901409A999999999901409A999999999901409A999999999901409A9999999999F13F6666666666661C40CDCCCCCCCCCC1C400102000000040000006666666666661C40CDCCCCCCCCCC1C403333333333333440333333333333344033333333333334409A9999999999F13F6666666666865140CDCCCCCCCC8C51400106000000020000000103000000010000000400000033333333333324403333333333333440333333333333344033333333333334403333333333333440333333333333244033333333333324403333333333333440010300000001000000040000009A9999999999F13F9A999999999901409A999999999901409A999999999901409A999999999901409A9999999999F13F9A9999999999F13F9A99999999990140');");
}
public override void InsertDataOperation_required_args()
@@ -164,9 +157,8 @@ public override void InsertDataOperation_required_args_multiple_rows()
AssertSql(
@"INSERT INTO `People` (`First Name`)
-VALUES ('John');
-INSERT INTO `People` (`First Name`)
-VALUES ('Daenerys');");
+VALUES ('John'),
+('Daenerys');");
}
public override void InsertDataOperation_throws_for_unsupported_column_types()
@@ -178,8 +170,31 @@ public override void DeleteDataOperation_all_args()
{
base.DeleteDataOperation_all_args();
- AssertSql(
- @"DELETE FROM `People`
+ if (AppConfig.ServerVersion.Supports.Returning)
+ {
+ AssertSql(
+"""
+DELETE FROM `People`
+WHERE `First Name` = 'Hodor'
+RETURNING 1;
+DELETE FROM `People`
+WHERE `First Name` = 'Daenerys'
+RETURNING 1;
+DELETE FROM `People`
+WHERE `First Name` = 'John'
+RETURNING 1;
+DELETE FROM `People`
+WHERE `First Name` = 'Arya'
+RETURNING 1;
+DELETE FROM `People`
+WHERE `First Name` = 'Harry'
+RETURNING 1;
+""");
+ }
+ else
+ {
+ AssertSql(
+ @"DELETE FROM `People`
WHERE `First Name` = 'Hodor';
SELECT ROW_COUNT();
@@ -198,14 +213,38 @@ DELETE FROM `People`
DELETE FROM `People`
WHERE `First Name` = 'Harry';
SELECT ROW_COUNT();");
+ }
}
public override void DeleteDataOperation_all_args_composite()
{
base.DeleteDataOperation_all_args_composite();
- AssertSql(
- @"DELETE FROM `People`
+ if (AppConfig.ServerVersion.Supports.Returning)
+ {
+ AssertSql(
+"""
+DELETE FROM `People`
+WHERE `First Name` = 'Hodor' AND `Last Name` IS NULL
+RETURNING 1;
+DELETE FROM `People`
+WHERE `First Name` = 'Daenerys' AND `Last Name` = 'Targaryen'
+RETURNING 1;
+DELETE FROM `People`
+WHERE `First Name` = 'John' AND `Last Name` = 'Snow'
+RETURNING 1;
+DELETE FROM `People`
+WHERE `First Name` = 'Arya' AND `Last Name` = 'Stark'
+RETURNING 1;
+DELETE FROM `People`
+WHERE `First Name` = 'Harry' AND `Last Name` = 'Strickland'
+RETURNING 1;
+""");
+ }
+ else
+ {
+ AssertSql(
+ @"DELETE FROM `People`
WHERE `First Name` = 'Hodor' AND `Last Name` IS NULL;
SELECT ROW_COUNT();
@@ -224,26 +263,52 @@ DELETE FROM `People`
DELETE FROM `People`
WHERE `First Name` = 'Harry' AND `Last Name` = 'Strickland';
SELECT ROW_COUNT();");
+ }
}
public override void DeleteDataOperation_required_args()
{
base.DeleteDataOperation_required_args();
- AssertSql(
- @"DELETE FROM `People`
+ if (AppConfig.ServerVersion.Supports.Returning)
+ {
+ AssertSql(
+"""
+DELETE FROM `People`
+WHERE `Last Name` = 'Snow'
+RETURNING 1;
+""");
+ }
+ else
+ {
+ AssertSql(
+ @"DELETE FROM `People`
WHERE `Last Name` = 'Snow';
SELECT ROW_COUNT();");
+ }
}
public override void DeleteDataOperation_required_args_composite()
{
base.DeleteDataOperation_required_args_composite();
- AssertSql(
- @"DELETE FROM `People`
+ if (AppConfig.ServerVersion.Supports.Returning)
+ {
+ AssertSql(
+"""
+DELETE FROM `People`
+WHERE `First Name` = 'John' AND `Last Name` = 'Snow'
+RETURNING 1;
+""");
+ }
+ else
+ {
+ AssertSql(
+ @"DELETE FROM `People`
WHERE `First Name` = 'John' AND `Last Name` = 'Snow';
SELECT ROW_COUNT();");
+ }
+
}
public override void UpdateDataOperation_all_args()
@@ -363,6 +428,17 @@ public override void DefaultValue_with_line_breaks(bool isUnicode)
);");
}
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.DefaultExpression), nameof(ServerVersionSupport.AlternativeDefaultExpression))]
+ public override void DefaultValue_with_line_breaks_2(bool isUnicode)
+ {
+ base.DefaultValue_with_line_breaks_2(isUnicode);
+
+ AssertSql(
+ @"CREATE TABLE `TestLineBreaks` (
+ `TestDefaultValue` longtext NOT NULL DEFAULT (CONCAT('0', CHAR(13, 10), '1', CHAR(13, 10), '2', CHAR(13, 10), '3', CHAR(13, 10), '4', CHAR(13, 10), '5', CHAR(13, 10), '6', CHAR(13, 10), '7', CHAR(13, 10), '8', CHAR(13, 10), '9', CHAR(13, 10), '10', CHAR(13, 10), '11', CHAR(13, 10), '12', CHAR(13, 10), '13', CHAR(13, 10), '14', CHAR(13, 10), '15', CHAR(13, 10), '16', CHAR(13, 10), '17', CHAR(13, 10), '18', CHAR(13, 10), '19', CHAR(13, 10), '20', CHAR(13, 10), '21', CHAR(13, 10), '22', CHAR(13, 10), '23', CHAR(13, 10), '24', CHAR(13, 10), '25', CHAR(13, 10), '26', CHAR(13, 10), '27', CHAR(13, 10), '28', CHAR(13, 10), '29', CHAR(13, 10), '30', CHAR(13, 10), '31', CHAR(13, 10), '32', CHAR(13, 10), '33', CHAR(13, 10), '34', CHAR(13, 10), '35', CHAR(13, 10), '36', CHAR(13, 10), '37', CHAR(13, 10), '38', CHAR(13, 10), '39', CHAR(13, 10), '40', CHAR(13, 10), '41', CHAR(13, 10), '42', CHAR(13, 10), '43', CHAR(13, 10), '44', CHAR(13, 10), '45', CHAR(13, 10), '46', CHAR(13, 10), '47', CHAR(13, 10), '48', CHAR(13, 10), '49', CHAR(13, 10), '50', CHAR(13, 10), '51', CHAR(13, 10), '52', CHAR(13, 10), '53', CHAR(13, 10), '54', CHAR(13, 10), '55', CHAR(13, 10), '56', CHAR(13, 10), '57', CHAR(13, 10), '58', CHAR(13, 10), '59', CHAR(13, 10), '60', CHAR(13, 10), '61', CHAR(13, 10), '62', CHAR(13, 10), '63', CHAR(13, 10), '64', CHAR(13, 10), '65', CHAR(13, 10), '66', CHAR(13, 10), '67', CHAR(13, 10), '68', CHAR(13, 10), '69', CHAR(13, 10), '70', CHAR(13, 10), '71', CHAR(13, 10), '72', CHAR(13, 10), '73', CHAR(13, 10), '74', CHAR(13, 10), '75', CHAR(13, 10), '76', CHAR(13, 10), '77', CHAR(13, 10), '78', CHAR(13, 10), '79', CHAR(13, 10), '80', CHAR(13, 10), '81', CHAR(13, 10), '82', CHAR(13, 10), '83', CHAR(13, 10), '84', CHAR(13, 10), '85', CHAR(13, 10), '86', CHAR(13, 10), '87', CHAR(13, 10), '88', CHAR(13, 10), '89', CHAR(13, 10), '90', CHAR(13, 10), '91', CHAR(13, 10), '92', CHAR(13, 10), '93', CHAR(13, 10), '94', CHAR(13, 10), '95', CHAR(13, 10), '96', CHAR(13, 10), '97', CHAR(13, 10), '98', CHAR(13, 10), '99', CHAR(13, 10), '100', CHAR(13, 10), '101', CHAR(13, 10), '102', CHAR(13, 10), '103', CHAR(13, 10), '104', CHAR(13, 10), '105', CHAR(13, 10), '106', CHAR(13, 10), '107', CHAR(13, 10), '108', CHAR(13, 10), '109', CHAR(13, 10), '110', CHAR(13, 10), '111', CHAR(13, 10), '112', CHAR(13, 10), '113', CHAR(13, 10), '114', CHAR(13, 10), '115', CHAR(13, 10), '116', CHAR(13, 10), '117', CHAR(13, 10), '118', CHAR(13, 10), '119', CHAR(13, 10), '120', CHAR(13, 10), '121', CHAR(13, 10), '122', CHAR(13, 10), '123', CHAR(13, 10), '124', CHAR(13, 10), '125', CHAR(13, 10), '126', CHAR(13, 10), '127', CHAR(13, 10), '128', CHAR(13, 10), '129', CHAR(13, 10), '130', CHAR(13, 10), '131', CHAR(13, 10), '132', CHAR(13, 10), '133', CHAR(13, 10), '134', CHAR(13, 10), '135', CHAR(13, 10), '136', CHAR(13, 10), '137', CHAR(13, 10), '138', CHAR(13, 10), '139', CHAR(13, 10), '140', CHAR(13, 10), '141', CHAR(13, 10), '142', CHAR(13, 10), '143', CHAR(13, 10), '144', CHAR(13, 10), '145', CHAR(13, 10), '146', CHAR(13, 10), '147', CHAR(13, 10), '148', CHAR(13, 10), '149', CHAR(13, 10), '150', CHAR(13, 10), '151', CHAR(13, 10), '152', CHAR(13, 10), '153', CHAR(13, 10), '154', CHAR(13, 10), '155', CHAR(13, 10), '156', CHAR(13, 10), '157', CHAR(13, 10), '158', CHAR(13, 10), '159', CHAR(13, 10), '160', CHAR(13, 10), '161', CHAR(13, 10), '162', CHAR(13, 10), '163', CHAR(13, 10), '164', CHAR(13, 10), '165', CHAR(13, 10), '166', CHAR(13, 10), '167', CHAR(13, 10), '168', CHAR(13, 10), '169', CHAR(13, 10), '170', CHAR(13, 10), '171', CHAR(13, 10), '172', CHAR(13, 10), '173', CHAR(13, 10), '174', CHAR(13, 10), '175', CHAR(13, 10), '176', CHAR(13, 10), '177', CHAR(13, 10), '178', CHAR(13, 10), '179', CHAR(13, 10), '180', CHAR(13, 10), '181', CHAR(13, 10), '182', CHAR(13, 10), '183', CHAR(13, 10), '184', CHAR(13, 10), '185', CHAR(13, 10), '186', CHAR(13, 10), '187', CHAR(13, 10), '188', CHAR(13, 10), '189', CHAR(13, 10), '190', CHAR(13, 10), '191', CHAR(13, 10), '192', CHAR(13, 10), '193', CHAR(13, 10), '194', CHAR(13, 10), '195', CHAR(13, 10), '196', CHAR(13, 10), '197', CHAR(13, 10), '198', CHAR(13, 10), '199', CHAR(13, 10), '200', CHAR(13, 10), '201', CHAR(13, 10), '202', CHAR(13, 10), '203', CHAR(13, 10), '204', CHAR(13, 10), '205', CHAR(13, 10), '206', CHAR(13, 10), '207', CHAR(13, 10), '208', CHAR(13, 10), '209', CHAR(13, 10), '210', CHAR(13, 10), '211', CHAR(13, 10), '212', CHAR(13, 10), '213', CHAR(13, 10), '214', CHAR(13, 10), '215', CHAR(13, 10), '216', CHAR(13, 10), '217', CHAR(13, 10), '218', CHAR(13, 10), '219', CHAR(13, 10), '220', CHAR(13, 10), '221', CHAR(13, 10), '222', CHAR(13, 10), '223', CHAR(13, 10), '224', CHAR(13, 10), '225', CHAR(13, 10), '226', CHAR(13, 10), '227', CHAR(13, 10), '228', CHAR(13, 10), '229', CHAR(13, 10), '230', CHAR(13, 10), '231', CHAR(13, 10), '232', CHAR(13, 10), '233', CHAR(13, 10), '234', CHAR(13, 10), '235', CHAR(13, 10), '236', CHAR(13, 10), '237', CHAR(13, 10), '238', CHAR(13, 10), '239', CHAR(13, 10), '240', CHAR(13, 10), '241', CHAR(13, 10), '242', CHAR(13, 10), '243', CHAR(13, 10), '244', CHAR(13, 10), '245', CHAR(13, 10), '246', CHAR(13, 10), '247', CHAR(13, 10), '248', CHAR(13, 10), '249', CHAR(13, 10), '250', CHAR(13, 10), '251', CHAR(13, 10), '252', CHAR(13, 10), '253', CHAR(13, 10), '254', CHAR(13, 10), '255', CHAR(13, 10), '256', CHAR(13, 10), '257', CHAR(13, 10), '258', CHAR(13, 10), '259', CHAR(13, 10), '260', CHAR(13, 10), '261', CHAR(13, 10), '262', CHAR(13, 10), '263', CHAR(13, 10), '264', CHAR(13, 10), '265', CHAR(13, 10), '266', CHAR(13, 10), '267', CHAR(13, 10), '268', CHAR(13, 10), '269', CHAR(13, 10), '270', CHAR(13, 10), '271', CHAR(13, 10), '272', CHAR(13, 10), '273', CHAR(13, 10), '274', CHAR(13, 10), '275', CHAR(13, 10), '276', CHAR(13, 10), '277', CHAR(13, 10), '278', CHAR(13, 10), '279', CHAR(13, 10), '280', CHAR(13, 10), '281', CHAR(13, 10), '282', CHAR(13, 10), '283', CHAR(13, 10), '284', CHAR(13, 10), '285', CHAR(13, 10), '286', CHAR(13, 10), '287', CHAR(13, 10), '288', CHAR(13, 10), '289', CHAR(13, 10), '290', CHAR(13, 10), '291', CHAR(13, 10), '292', CHAR(13, 10), '293', CHAR(13, 10), '294', CHAR(13, 10), '295', CHAR(13, 10), '296', CHAR(13, 10), '297', CHAR(13, 10), '298', CHAR(13, 10), '299', CHAR(13, 10), ''))
+);");
+ }
+
[ConditionalFact]
[SupportedServerVersionLessThanCondition(nameof(ServerVersionSupport.DefaultExpression), nameof(ServerVersionSupport.AlternativeDefaultExpression))]
public virtual void DefaultValue_not_generated_for_unlimited_text_column_missing_default_expression_support()
diff --git a/test/EFCore.MySql.FunctionalTests/ProxyGraphUpdatesMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/ProxyGraphUpdatesMySqlTest.cs
index 7a27b7ac3..31c13a57e 100644
--- a/test/EFCore.MySql.FunctionalTests/ProxyGraphUpdatesMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/ProxyGraphUpdatesMySqlTest.cs
@@ -17,11 +17,6 @@ protected ProxyGraphUpdatesMySqlTestBase(TFixture fixture)
{
}
- // Needs lazy-loading
- public override void Attempting_to_save_two_entity_cycle_with_lazy_loading_throws()
- {
- }
-
protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction)
=> facade.UseTransaction(transaction.GetDbTransaction());
@@ -67,6 +62,11 @@ public ChangeTracking(ProxyGraphUpdatesWithChangeTrackingMySqlFixture fixture)
{
}
+ // Needs lazy loading
+ public override void Save_two_entity_cycle_with_lazy_loading()
+ {
+ }
+
protected override bool DoesLazyLoading
=> false;
diff --git a/test/EFCore.MySql.FunctionalTests/Query/ComplexNavigationsCollectionsQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/ComplexNavigationsCollectionsQueryMySqlTest.cs
index 7329370cf..74ebe3106 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/ComplexNavigationsCollectionsQueryMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/ComplexNavigationsCollectionsQueryMySqlTest.cs
@@ -835,7 +835,7 @@ public override async Task Select_subquery_single_nested_subquery(bool async)
await base.Select_subquery_single_nested_subquery(async);
AssertSql(
- @"SELECT `l`.`Id`, `t0`.`Id`, `t1`.`Id`, `t0`.`c`
+ @"SELECT `l`.`Id`, `t0`.`Id`, `l1`.`Id`, `t0`.`c`
FROM `LevelOne` AS `l`
LEFT JOIN (
SELECT `t`.`c`, `t`.`Id`, `t`.`OneToMany_Optional_Inverse2Id`
@@ -845,11 +845,8 @@ LEFT JOIN (
) AS `t`
WHERE `t`.`row` <= 1
) AS `t0` ON `l`.`Id` = `t0`.`OneToMany_Optional_Inverse2Id`
-LEFT JOIN (
- SELECT `l1`.`Id`, `l1`.`OneToMany_Optional_Inverse3Id`
- FROM `LevelThree` AS `l1`
-) AS `t1` ON `t0`.`Id` = `t1`.`OneToMany_Optional_Inverse3Id`
-ORDER BY `l`.`Id`, `t0`.`Id`, `t1`.`Id`");
+LEFT JOIN `LevelThree` AS `l1` ON `t0`.`Id` = `l1`.`OneToMany_Optional_Inverse3Id`
+ORDER BY `l`.`Id`, `t0`.`Id`, `l1`.`Id`");
}
public override async Task Select_subquery_single_nested_subquery2(bool async)
@@ -857,25 +854,22 @@ public override async Task Select_subquery_single_nested_subquery2(bool async)
await base.Select_subquery_single_nested_subquery2(async);
AssertSql(
- $@"SELECT `l`.`Id`, `t2`.`Id`, `t2`.`Id0`, `t2`.`Id1`, `t2`.`c`
+ $@"SELECT `l`.`Id`, `t1`.`Id`, `t1`.`Id0`, `t1`.`Id1`, `t1`.`c`
FROM `LevelOne` AS `l`
LEFT JOIN (
- SELECT `l0`.`Id`, `t0`.`Id` AS `Id0`, `t1`.`Id` AS `Id1`, `t0`.`c`, `l0`.`OneToMany_Optional_Inverse2Id`
+ SELECT `l0`.`Id`, `t0`.`Id` AS `Id0`, `l1`.`Id` AS `Id1`, `t0`.`c`, `l0`.`OneToMany_Optional_Inverse2Id`
FROM `LevelTwo` AS `l0`
LEFT JOIN (
SELECT `t`.`c`, `t`.`Id`, `t`.`OneToMany_Optional_Inverse3Id`
FROM (
- SELECT {(AppConfig.ServerVersion.Supports.MySqlBug96947Workaround ? "CAST(1 AS signed)" : "1")} AS `c`, `l1`.`Id`, `l1`.`OneToMany_Optional_Inverse3Id`, ROW_NUMBER() OVER(PARTITION BY `l1`.`OneToMany_Optional_Inverse3Id` ORDER BY `l1`.`Id`) AS `row`
- FROM `LevelThree` AS `l1`
+ SELECT {(AppConfig.ServerVersion.Supports.MySqlBug96947Workaround ? "CAST(1 AS signed)" : "1")} AS `c`, `l2`.`Id`, `l2`.`OneToMany_Optional_Inverse3Id`, ROW_NUMBER() OVER(PARTITION BY `l2`.`OneToMany_Optional_Inverse3Id` ORDER BY `l2`.`Id`) AS `row`
+ FROM `LevelThree` AS `l2`
) AS `t`
WHERE `t`.`row` <= 1
) AS `t0` ON `l0`.`Id` = `t0`.`OneToMany_Optional_Inverse3Id`
- LEFT JOIN (
- SELECT `l2`.`Id`, `l2`.`OneToMany_Optional_Inverse4Id`
- FROM `LevelFour` AS `l2`
- ) AS `t1` ON `t0`.`Id` = `t1`.`OneToMany_Optional_Inverse4Id`
-) AS `t2` ON `l`.`Id` = `t2`.`OneToMany_Optional_Inverse2Id`
-ORDER BY `l`.`Id`, `t2`.`Id`, `t2`.`Id0`, `t2`.`Id1`");
+ LEFT JOIN `LevelFour` AS `l1` ON `t0`.`Id` = `l1`.`OneToMany_Optional_Inverse4Id`
+) AS `t1` ON `l`.`Id` = `t1`.`OneToMany_Optional_Inverse2Id`
+ORDER BY `l`.`Id`, `t1`.`Id`, `t1`.`Id0`, `t1`.`Id1`");
}
public override async Task Filtered_include_basic_Where(bool async)
@@ -898,13 +892,10 @@ public override async Task Filtered_include_OrderBy(bool async)
await base.Filtered_include_OrderBy(async);
AssertSql(
- @"SELECT `l`.`Id`, `l`.`Date`, `l`.`Name`, `l`.`OneToMany_Optional_Self_Inverse1Id`, `l`.`OneToMany_Required_Self_Inverse1Id`, `l`.`OneToOne_Optional_Self1Id`, `t`.`Id`, `t`.`Date`, `t`.`Level1_Optional_Id`, `t`.`Level1_Required_Id`, `t`.`Name`, `t`.`OneToMany_Optional_Inverse2Id`, `t`.`OneToMany_Optional_Self_Inverse2Id`, `t`.`OneToMany_Required_Inverse2Id`, `t`.`OneToMany_Required_Self_Inverse2Id`, `t`.`OneToOne_Optional_PK_Inverse2Id`, `t`.`OneToOne_Optional_Self2Id`
+ @"SELECT `l`.`Id`, `l`.`Date`, `l`.`Name`, `l`.`OneToMany_Optional_Self_Inverse1Id`, `l`.`OneToMany_Required_Self_Inverse1Id`, `l`.`OneToOne_Optional_Self1Id`, `l0`.`Id`, `l0`.`Date`, `l0`.`Level1_Optional_Id`, `l0`.`Level1_Required_Id`, `l0`.`Name`, `l0`.`OneToMany_Optional_Inverse2Id`, `l0`.`OneToMany_Optional_Self_Inverse2Id`, `l0`.`OneToMany_Required_Inverse2Id`, `l0`.`OneToMany_Required_Self_Inverse2Id`, `l0`.`OneToOne_Optional_PK_Inverse2Id`, `l0`.`OneToOne_Optional_Self2Id`
FROM `LevelOne` AS `l`
-LEFT JOIN (
- SELECT `l0`.`Id`, `l0`.`Date`, `l0`.`Level1_Optional_Id`, `l0`.`Level1_Required_Id`, `l0`.`Name`, `l0`.`OneToMany_Optional_Inverse2Id`, `l0`.`OneToMany_Optional_Self_Inverse2Id`, `l0`.`OneToMany_Required_Inverse2Id`, `l0`.`OneToMany_Required_Self_Inverse2Id`, `l0`.`OneToOne_Optional_PK_Inverse2Id`, `l0`.`OneToOne_Optional_Self2Id`
- FROM `LevelTwo` AS `l0`
-) AS `t` ON `l`.`Id` = `t`.`OneToMany_Optional_Inverse2Id`
-ORDER BY `l`.`Id`, `t`.`Name`");
+LEFT JOIN `LevelTwo` AS `l0` ON `l`.`Id` = `l0`.`OneToMany_Optional_Inverse2Id`
+ORDER BY `l`.`Id`, `l0`.`Name`");
}
public override async Task Filtered_ThenInclude_OrderBy(bool async)
@@ -912,17 +903,14 @@ public override async Task Filtered_ThenInclude_OrderBy(bool async)
await base.Filtered_ThenInclude_OrderBy(async);
AssertSql(
- @"SELECT `l`.`Id`, `l`.`Date`, `l`.`Name`, `l`.`OneToMany_Optional_Self_Inverse1Id`, `l`.`OneToMany_Required_Self_Inverse1Id`, `l`.`OneToOne_Optional_Self1Id`, `t0`.`Id`, `t0`.`Date`, `t0`.`Level1_Optional_Id`, `t0`.`Level1_Required_Id`, `t0`.`Name`, `t0`.`OneToMany_Optional_Inverse2Id`, `t0`.`OneToMany_Optional_Self_Inverse2Id`, `t0`.`OneToMany_Required_Inverse2Id`, `t0`.`OneToMany_Required_Self_Inverse2Id`, `t0`.`OneToOne_Optional_PK_Inverse2Id`, `t0`.`OneToOne_Optional_Self2Id`, `t0`.`Id0`, `t0`.`Level2_Optional_Id`, `t0`.`Level2_Required_Id`, `t0`.`Name0`, `t0`.`OneToMany_Optional_Inverse3Id`, `t0`.`OneToMany_Optional_Self_Inverse3Id`, `t0`.`OneToMany_Required_Inverse3Id`, `t0`.`OneToMany_Required_Self_Inverse3Id`, `t0`.`OneToOne_Optional_PK_Inverse3Id`, `t0`.`OneToOne_Optional_Self3Id`
+ @"SELECT `l`.`Id`, `l`.`Date`, `l`.`Name`, `l`.`OneToMany_Optional_Self_Inverse1Id`, `l`.`OneToMany_Required_Self_Inverse1Id`, `l`.`OneToOne_Optional_Self1Id`, `t`.`Id`, `t`.`Date`, `t`.`Level1_Optional_Id`, `t`.`Level1_Required_Id`, `t`.`Name`, `t`.`OneToMany_Optional_Inverse2Id`, `t`.`OneToMany_Optional_Self_Inverse2Id`, `t`.`OneToMany_Required_Inverse2Id`, `t`.`OneToMany_Required_Self_Inverse2Id`, `t`.`OneToOne_Optional_PK_Inverse2Id`, `t`.`OneToOne_Optional_Self2Id`, `t`.`Id0`, `t`.`Level2_Optional_Id`, `t`.`Level2_Required_Id`, `t`.`Name0`, `t`.`OneToMany_Optional_Inverse3Id`, `t`.`OneToMany_Optional_Self_Inverse3Id`, `t`.`OneToMany_Required_Inverse3Id`, `t`.`OneToMany_Required_Self_Inverse3Id`, `t`.`OneToOne_Optional_PK_Inverse3Id`, `t`.`OneToOne_Optional_Self3Id`
FROM `LevelOne` AS `l`
LEFT JOIN (
- SELECT `l0`.`Id`, `l0`.`Date`, `l0`.`Level1_Optional_Id`, `l0`.`Level1_Required_Id`, `l0`.`Name`, `l0`.`OneToMany_Optional_Inverse2Id`, `l0`.`OneToMany_Optional_Self_Inverse2Id`, `l0`.`OneToMany_Required_Inverse2Id`, `l0`.`OneToMany_Required_Self_Inverse2Id`, `l0`.`OneToOne_Optional_PK_Inverse2Id`, `l0`.`OneToOne_Optional_Self2Id`, `t`.`Id` AS `Id0`, `t`.`Level2_Optional_Id`, `t`.`Level2_Required_Id`, `t`.`Name` AS `Name0`, `t`.`OneToMany_Optional_Inverse3Id`, `t`.`OneToMany_Optional_Self_Inverse3Id`, `t`.`OneToMany_Required_Inverse3Id`, `t`.`OneToMany_Required_Self_Inverse3Id`, `t`.`OneToOne_Optional_PK_Inverse3Id`, `t`.`OneToOne_Optional_Self3Id`
+ SELECT `l0`.`Id`, `l0`.`Date`, `l0`.`Level1_Optional_Id`, `l0`.`Level1_Required_Id`, `l0`.`Name`, `l0`.`OneToMany_Optional_Inverse2Id`, `l0`.`OneToMany_Optional_Self_Inverse2Id`, `l0`.`OneToMany_Required_Inverse2Id`, `l0`.`OneToMany_Required_Self_Inverse2Id`, `l0`.`OneToOne_Optional_PK_Inverse2Id`, `l0`.`OneToOne_Optional_Self2Id`, `l1`.`Id` AS `Id0`, `l1`.`Level2_Optional_Id`, `l1`.`Level2_Required_Id`, `l1`.`Name` AS `Name0`, `l1`.`OneToMany_Optional_Inverse3Id`, `l1`.`OneToMany_Optional_Self_Inverse3Id`, `l1`.`OneToMany_Required_Inverse3Id`, `l1`.`OneToMany_Required_Self_Inverse3Id`, `l1`.`OneToOne_Optional_PK_Inverse3Id`, `l1`.`OneToOne_Optional_Self3Id`
FROM `LevelTwo` AS `l0`
- LEFT JOIN (
- SELECT `l1`.`Id`, `l1`.`Level2_Optional_Id`, `l1`.`Level2_Required_Id`, `l1`.`Name`, `l1`.`OneToMany_Optional_Inverse3Id`, `l1`.`OneToMany_Optional_Self_Inverse3Id`, `l1`.`OneToMany_Required_Inverse3Id`, `l1`.`OneToMany_Required_Self_Inverse3Id`, `l1`.`OneToOne_Optional_PK_Inverse3Id`, `l1`.`OneToOne_Optional_Self3Id`
- FROM `LevelThree` AS `l1`
- ) AS `t` ON `l0`.`Id` = `t`.`OneToMany_Optional_Inverse3Id`
-) AS `t0` ON `l`.`Id` = `t0`.`OneToMany_Optional_Inverse2Id`
-ORDER BY `l`.`Id`, `t0`.`Id`, `t0`.`Name0`");
+ LEFT JOIN `LevelThree` AS `l1` ON `l0`.`Id` = `l1`.`OneToMany_Optional_Inverse3Id`
+) AS `t` ON `l`.`Id` = `t`.`OneToMany_Optional_Inverse2Id`
+ORDER BY `l`.`Id`, `t`.`Id`, `t`.`Name0`");
}
public override async Task Filtered_include_ThenInclude_OrderBy(bool async)
@@ -930,17 +918,14 @@ public override async Task Filtered_include_ThenInclude_OrderBy(bool async)
await base.Filtered_include_ThenInclude_OrderBy(async);
AssertSql(
- @"SELECT `l`.`Id`, `l`.`Date`, `l`.`Name`, `l`.`OneToMany_Optional_Self_Inverse1Id`, `l`.`OneToMany_Required_Self_Inverse1Id`, `l`.`OneToOne_Optional_Self1Id`, `t0`.`Id`, `t0`.`Date`, `t0`.`Level1_Optional_Id`, `t0`.`Level1_Required_Id`, `t0`.`Name`, `t0`.`OneToMany_Optional_Inverse2Id`, `t0`.`OneToMany_Optional_Self_Inverse2Id`, `t0`.`OneToMany_Required_Inverse2Id`, `t0`.`OneToMany_Required_Self_Inverse2Id`, `t0`.`OneToOne_Optional_PK_Inverse2Id`, `t0`.`OneToOne_Optional_Self2Id`, `t0`.`Id0`, `t0`.`Level2_Optional_Id`, `t0`.`Level2_Required_Id`, `t0`.`Name0`, `t0`.`OneToMany_Optional_Inverse3Id`, `t0`.`OneToMany_Optional_Self_Inverse3Id`, `t0`.`OneToMany_Required_Inverse3Id`, `t0`.`OneToMany_Required_Self_Inverse3Id`, `t0`.`OneToOne_Optional_PK_Inverse3Id`, `t0`.`OneToOne_Optional_Self3Id`
+ @"SELECT `l`.`Id`, `l`.`Date`, `l`.`Name`, `l`.`OneToMany_Optional_Self_Inverse1Id`, `l`.`OneToMany_Required_Self_Inverse1Id`, `l`.`OneToOne_Optional_Self1Id`, `t`.`Id`, `t`.`Date`, `t`.`Level1_Optional_Id`, `t`.`Level1_Required_Id`, `t`.`Name`, `t`.`OneToMany_Optional_Inverse2Id`, `t`.`OneToMany_Optional_Self_Inverse2Id`, `t`.`OneToMany_Required_Inverse2Id`, `t`.`OneToMany_Required_Self_Inverse2Id`, `t`.`OneToOne_Optional_PK_Inverse2Id`, `t`.`OneToOne_Optional_Self2Id`, `t`.`Id0`, `t`.`Level2_Optional_Id`, `t`.`Level2_Required_Id`, `t`.`Name0`, `t`.`OneToMany_Optional_Inverse3Id`, `t`.`OneToMany_Optional_Self_Inverse3Id`, `t`.`OneToMany_Required_Inverse3Id`, `t`.`OneToMany_Required_Self_Inverse3Id`, `t`.`OneToOne_Optional_PK_Inverse3Id`, `t`.`OneToOne_Optional_Self3Id`
FROM `LevelOne` AS `l`
LEFT JOIN (
- SELECT `l0`.`Id`, `l0`.`Date`, `l0`.`Level1_Optional_Id`, `l0`.`Level1_Required_Id`, `l0`.`Name`, `l0`.`OneToMany_Optional_Inverse2Id`, `l0`.`OneToMany_Optional_Self_Inverse2Id`, `l0`.`OneToMany_Required_Inverse2Id`, `l0`.`OneToMany_Required_Self_Inverse2Id`, `l0`.`OneToOne_Optional_PK_Inverse2Id`, `l0`.`OneToOne_Optional_Self2Id`, `t`.`Id` AS `Id0`, `t`.`Level2_Optional_Id`, `t`.`Level2_Required_Id`, `t`.`Name` AS `Name0`, `t`.`OneToMany_Optional_Inverse3Id`, `t`.`OneToMany_Optional_Self_Inverse3Id`, `t`.`OneToMany_Required_Inverse3Id`, `t`.`OneToMany_Required_Self_Inverse3Id`, `t`.`OneToOne_Optional_PK_Inverse3Id`, `t`.`OneToOne_Optional_Self3Id`
+ SELECT `l0`.`Id`, `l0`.`Date`, `l0`.`Level1_Optional_Id`, `l0`.`Level1_Required_Id`, `l0`.`Name`, `l0`.`OneToMany_Optional_Inverse2Id`, `l0`.`OneToMany_Optional_Self_Inverse2Id`, `l0`.`OneToMany_Required_Inverse2Id`, `l0`.`OneToMany_Required_Self_Inverse2Id`, `l0`.`OneToOne_Optional_PK_Inverse2Id`, `l0`.`OneToOne_Optional_Self2Id`, `l1`.`Id` AS `Id0`, `l1`.`Level2_Optional_Id`, `l1`.`Level2_Required_Id`, `l1`.`Name` AS `Name0`, `l1`.`OneToMany_Optional_Inverse3Id`, `l1`.`OneToMany_Optional_Self_Inverse3Id`, `l1`.`OneToMany_Required_Inverse3Id`, `l1`.`OneToMany_Required_Self_Inverse3Id`, `l1`.`OneToOne_Optional_PK_Inverse3Id`, `l1`.`OneToOne_Optional_Self3Id`
FROM `LevelTwo` AS `l0`
- LEFT JOIN (
- SELECT `l1`.`Id`, `l1`.`Level2_Optional_Id`, `l1`.`Level2_Required_Id`, `l1`.`Name`, `l1`.`OneToMany_Optional_Inverse3Id`, `l1`.`OneToMany_Optional_Self_Inverse3Id`, `l1`.`OneToMany_Required_Inverse3Id`, `l1`.`OneToMany_Required_Self_Inverse3Id`, `l1`.`OneToOne_Optional_PK_Inverse3Id`, `l1`.`OneToOne_Optional_Self3Id`
- FROM `LevelThree` AS `l1`
- ) AS `t` ON `l0`.`Id` = `t`.`OneToMany_Optional_Inverse3Id`
-) AS `t0` ON `l`.`Id` = `t0`.`OneToMany_Optional_Inverse2Id`
-ORDER BY `l`.`Id`, `t0`.`Name`, `t0`.`Id`, `t0`.`Name0` DESC");
+ LEFT JOIN `LevelThree` AS `l1` ON `l0`.`Id` = `l1`.`OneToMany_Optional_Inverse3Id`
+) AS `t` ON `l`.`Id` = `t`.`OneToMany_Optional_Inverse2Id`
+ORDER BY `l`.`Id`, `t`.`Name`, `t`.`Id`, `t`.`Name0` DESC");
}
public override async Task Filtered_include_basic_OrderBy_Take(bool async)
@@ -1012,7 +997,7 @@ LEFT JOIN (
) AS `t`
WHERE 1 < `t`.`row`
) AS `t0` ON `l`.`Id` = `t0`.`OneToMany_Optional_Inverse2Id`
-ORDER BY `l`.`Id`, `t0`.`OneToMany_Optional_Inverse2Id`, `t0`.`Id`");
+ORDER BY `l`.`Id`");
}
public override async Task Filtered_include_Take_without_OrderBy(bool async)
@@ -1030,7 +1015,7 @@ LEFT JOIN (
) AS `t`
WHERE `t`.`row` <= 1
) AS `t0` ON `l`.`Id` = `t0`.`OneToMany_Optional_Inverse2Id`
-ORDER BY `l`.`Id`, `t0`.`OneToMany_Optional_Inverse2Id`, `t0`.`Id`");
+ORDER BY `l`.`Id`");
}
public override async Task Filtered_include_on_ThenInclude(bool async)
diff --git a/test/EFCore.MySql.FunctionalTests/Query/ComplexNavigationsCollectionsSharedTypeQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/ComplexNavigationsCollectionsSharedTypeQueryMySqlTest.cs
index afb4e56b6..f149fa8c7 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/ComplexNavigationsCollectionsSharedTypeQueryMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/ComplexNavigationsCollectionsSharedTypeQueryMySqlTest.cs
@@ -21,28 +21,21 @@ public override async Task SelectMany_with_Include1(bool async)
await base.SelectMany_with_Include1(async);
AssertSql(
- @"SELECT `t`.`Id`, `t`.`OneToOne_Required_PK_Date`, `t`.`Level1_Optional_Id`, `t`.`Level1_Required_Id`, `t`.`Level2_Name`, `t`.`OneToMany_Optional_Inverse2Id`, `t`.`OneToMany_Required_Inverse2Id`, `t`.`OneToOne_Optional_PK_Inverse2Id`, `l`.`Id`, `t`.`Id0`, `t0`.`Id`, `t0`.`Level2_Optional_Id`, `t0`.`Level2_Required_Id`, `t0`.`Level3_Name`, `t0`.`OneToMany_Optional_Inverse3Id`, `t0`.`OneToMany_Required_Inverse3Id`, `t0`.`OneToOne_Optional_PK_Inverse3Id`, `t0`.`Id0`, `t0`.`Id00`
+ @"SELECT `t`.`Id`, `t`.`OneToOne_Required_PK_Date`, `t`.`Level1_Optional_Id`, `t`.`Level1_Required_Id`, `t`.`Level2_Name`, `t`.`OneToMany_Optional_Inverse2Id`, `t`.`OneToMany_Required_Inverse2Id`, `t`.`OneToOne_Optional_PK_Inverse2Id`, `l`.`Id`, `t0`.`Id`, `t0`.`Level2_Optional_Id`, `t0`.`Level2_Required_Id`, `t0`.`Level3_Name`, `t0`.`OneToMany_Optional_Inverse3Id`, `t0`.`OneToMany_Required_Inverse3Id`, `t0`.`OneToOne_Optional_PK_Inverse3Id`
FROM `Level1` AS `l`
INNER JOIN (
- SELECT `l0`.`Id`, `l0`.`OneToOne_Required_PK_Date`, `l0`.`Level1_Optional_Id`, `l0`.`Level1_Required_Id`, `l0`.`Level2_Name`, `l0`.`OneToMany_Optional_Inverse2Id`, `l0`.`OneToMany_Required_Inverse2Id`, `l0`.`OneToOne_Optional_PK_Inverse2Id`, `l1`.`Id` AS `Id0`
+ SELECT `l0`.`Id`, `l0`.`OneToOne_Required_PK_Date`, `l0`.`Level1_Optional_Id`, `l0`.`Level1_Required_Id`, `l0`.`Level2_Name`, `l0`.`OneToMany_Optional_Inverse2Id`, `l0`.`OneToMany_Required_Inverse2Id`, `l0`.`OneToOne_Optional_PK_Inverse2Id`
FROM `Level1` AS `l0`
- INNER JOIN `Level1` AS `l1` ON `l0`.`Id` = `l1`.`Id`
WHERE (`l0`.`OneToOne_Required_PK_Date` IS NOT NULL AND (`l0`.`Level1_Required_Id` IS NOT NULL)) AND `l0`.`OneToMany_Required_Inverse2Id` IS NOT NULL
) AS `t` ON `l`.`Id` = `t`.`OneToMany_Optional_Inverse2Id`
LEFT JOIN (
- SELECT `l2`.`Id`, `l2`.`Level2_Optional_Id`, `l2`.`Level2_Required_Id`, `l2`.`Level3_Name`, `l2`.`OneToMany_Optional_Inverse3Id`, `l2`.`OneToMany_Required_Inverse3Id`, `l2`.`OneToOne_Optional_PK_Inverse3Id`, `t1`.`Id` AS `Id0`, `t1`.`Id0` AS `Id00`
- FROM `Level1` AS `l2`
- INNER JOIN (
- SELECT `l3`.`Id`, `l4`.`Id` AS `Id0`
- FROM `Level1` AS `l3`
- INNER JOIN `Level1` AS `l4` ON `l3`.`Id` = `l4`.`Id`
- WHERE (`l3`.`OneToOne_Required_PK_Date` IS NOT NULL AND (`l3`.`Level1_Required_Id` IS NOT NULL)) AND `l3`.`OneToMany_Required_Inverse2Id` IS NOT NULL
- ) AS `t1` ON `l2`.`Id` = `t1`.`Id`
- WHERE `l2`.`Level2_Required_Id` IS NOT NULL AND (`l2`.`OneToMany_Required_Inverse3Id` IS NOT NULL)
+ SELECT `l1`.`Id`, `l1`.`Level2_Optional_Id`, `l1`.`Level2_Required_Id`, `l1`.`Level3_Name`, `l1`.`OneToMany_Optional_Inverse3Id`, `l1`.`OneToMany_Required_Inverse3Id`, `l1`.`OneToOne_Optional_PK_Inverse3Id`
+ FROM `Level1` AS `l1`
+ WHERE `l1`.`Level2_Required_Id` IS NOT NULL AND (`l1`.`OneToMany_Required_Inverse3Id` IS NOT NULL)
) AS `t0` ON CASE
WHEN (`t`.`OneToOne_Required_PK_Date` IS NOT NULL AND (`t`.`Level1_Required_Id` IS NOT NULL)) AND `t`.`OneToMany_Required_Inverse2Id` IS NOT NULL THEN `t`.`Id`
END = `t0`.`OneToMany_Optional_Inverse3Id`
-ORDER BY `l`.`Id`, `t`.`Id`, `t`.`Id0`, `t0`.`Id`, `t0`.`Id0`");
+ORDER BY `l`.`Id`, `t`.`Id`");
}
public override async Task SelectMany_with_navigation_and_Distinct(bool async)
@@ -50,22 +43,20 @@ public override async Task SelectMany_with_navigation_and_Distinct(bool async)
await base.SelectMany_with_navigation_and_Distinct(async);
AssertSql(
- @"SELECT `l`.`Id`, `l`.`Date`, `l`.`Name`, `t`.`Id`, `t0`.`Id`, `t0`.`OneToOne_Required_PK_Date`, `t0`.`Level1_Optional_Id`, `t0`.`Level1_Required_Id`, `t0`.`Level2_Name`, `t0`.`OneToMany_Optional_Inverse2Id`, `t0`.`OneToMany_Required_Inverse2Id`, `t0`.`OneToOne_Optional_PK_Inverse2Id`, `t0`.`Id0`
+ @"SELECT `l`.`Id`, `l`.`Date`, `l`.`Name`, `t`.`Id`, `t0`.`Id`, `t0`.`OneToOne_Required_PK_Date`, `t0`.`Level1_Optional_Id`, `t0`.`Level1_Required_Id`, `t0`.`Level2_Name`, `t0`.`OneToMany_Optional_Inverse2Id`, `t0`.`OneToMany_Required_Inverse2Id`, `t0`.`OneToOne_Optional_PK_Inverse2Id`
FROM `Level1` AS `l`
INNER JOIN (
SELECT DISTINCT `l0`.`Id`, `l0`.`OneToOne_Required_PK_Date`, `l0`.`Level1_Optional_Id`, `l0`.`Level1_Required_Id`, `l0`.`Level2_Name`, `l0`.`OneToMany_Optional_Inverse2Id`, `l0`.`OneToMany_Required_Inverse2Id`, `l0`.`OneToOne_Optional_PK_Inverse2Id`
FROM `Level1` AS `l0`
- INNER JOIN `Level1` AS `l1` ON `l0`.`Id` = `l1`.`Id`
WHERE (`l0`.`OneToOne_Required_PK_Date` IS NOT NULL AND (`l0`.`Level1_Required_Id` IS NOT NULL)) AND `l0`.`OneToMany_Required_Inverse2Id` IS NOT NULL
) AS `t` ON `l`.`Id` = `t`.`OneToMany_Optional_Inverse2Id`
LEFT JOIN (
- SELECT `l2`.`Id`, `l2`.`OneToOne_Required_PK_Date`, `l2`.`Level1_Optional_Id`, `l2`.`Level1_Required_Id`, `l2`.`Level2_Name`, `l2`.`OneToMany_Optional_Inverse2Id`, `l2`.`OneToMany_Required_Inverse2Id`, `l2`.`OneToOne_Optional_PK_Inverse2Id`, `l3`.`Id` AS `Id0`
- FROM `Level1` AS `l2`
- INNER JOIN `Level1` AS `l3` ON `l2`.`Id` = `l3`.`Id`
- WHERE (`l2`.`OneToOne_Required_PK_Date` IS NOT NULL AND (`l2`.`Level1_Required_Id` IS NOT NULL)) AND `l2`.`OneToMany_Required_Inverse2Id` IS NOT NULL
+ SELECT `l1`.`Id`, `l1`.`OneToOne_Required_PK_Date`, `l1`.`Level1_Optional_Id`, `l1`.`Level1_Required_Id`, `l1`.`Level2_Name`, `l1`.`OneToMany_Optional_Inverse2Id`, `l1`.`OneToMany_Required_Inverse2Id`, `l1`.`OneToOne_Optional_PK_Inverse2Id`
+ FROM `Level1` AS `l1`
+ WHERE (`l1`.`OneToOne_Required_PK_Date` IS NOT NULL AND (`l1`.`Level1_Required_Id` IS NOT NULL)) AND `l1`.`OneToMany_Required_Inverse2Id` IS NOT NULL
) AS `t0` ON `l`.`Id` = `t0`.`OneToMany_Optional_Inverse2Id`
WHERE (`t`.`OneToOne_Required_PK_Date` IS NOT NULL AND (`t`.`Level1_Required_Id` IS NOT NULL)) AND `t`.`OneToMany_Required_Inverse2Id` IS NOT NULL
-ORDER BY `l`.`Id`, `t`.`Id`, `t0`.`Id`");
+ORDER BY `l`.`Id`, `t`.`Id`");
}
public override async Task Take_Select_collection_Take(bool async)
@@ -75,7 +66,7 @@ public override async Task Take_Select_collection_Take(bool async)
AssertSql(
@"@__p_0='1'
-SELECT `t`.`Id`, `t`.`Name`, `t0`.`Id`, `t0`.`Name`, `t0`.`Level1Id`, `t0`.`Level2Id`, `t0`.`Id0`, `t0`.`Date`, `t0`.`Name0`, `t0`.`Id1`, `t0`.`Id00`
+SELECT `t`.`Id`, `t`.`Name`, `t0`.`Id`, `t0`.`Name`, `t0`.`Level1Id`, `t0`.`Level2Id`, `t0`.`Id0`, `t0`.`Date`, `t0`.`Name0`, `t0`.`Id1`
FROM (
SELECT `l`.`Id`, `l`.`Name`
FROM `Level1` AS `l`
@@ -85,22 +76,21 @@ LIMIT @__p_0
LEFT JOIN LATERAL (
SELECT CASE
WHEN (`t1`.`OneToOne_Required_PK_Date` IS NOT NULL AND (`t1`.`Level1_Required_Id` IS NOT NULL)) AND `t1`.`OneToMany_Required_Inverse2Id` IS NOT NULL THEN `t1`.`Id`
- END AS `Id`, `t1`.`Level2_Name` AS `Name`, `t1`.`OneToMany_Required_Inverse2Id` AS `Level1Id`, `t1`.`Level1_Required_Id` AS `Level2Id`, `l1`.`Id` AS `Id0`, `l1`.`Date`, `l1`.`Name` AS `Name0`, `t1`.`Id` AS `Id1`, `t1`.`Id0` AS `Id00`, `t1`.`c`
+ END AS `Id`, `t1`.`Level2_Name` AS `Name`, `t1`.`OneToMany_Required_Inverse2Id` AS `Level1Id`, `t1`.`Level1_Required_Id` AS `Level2Id`, `l0`.`Id` AS `Id0`, `l0`.`Date`, `l0`.`Name` AS `Name0`, `t1`.`Id` AS `Id1`, `t1`.`c`
FROM (
- SELECT `l0`.`Id`, `l0`.`OneToOne_Required_PK_Date`, `l0`.`Level1_Required_Id`, `l0`.`Level2_Name`, `l0`.`OneToMany_Required_Inverse2Id`, `l2`.`Id` AS `Id0`, CASE
- WHEN (`l0`.`OneToOne_Required_PK_Date` IS NOT NULL AND (`l0`.`Level1_Required_Id` IS NOT NULL)) AND `l0`.`OneToMany_Required_Inverse2Id` IS NOT NULL THEN `l0`.`Id`
+ SELECT `l1`.`Id`, `l1`.`OneToOne_Required_PK_Date`, `l1`.`Level1_Required_Id`, `l1`.`Level2_Name`, `l1`.`OneToMany_Required_Inverse2Id`, CASE
+ WHEN (`l1`.`OneToOne_Required_PK_Date` IS NOT NULL AND (`l1`.`Level1_Required_Id` IS NOT NULL)) AND `l1`.`OneToMany_Required_Inverse2Id` IS NOT NULL THEN `l1`.`Id`
END AS `c`
- FROM `Level1` AS `l0`
- INNER JOIN `Level1` AS `l2` ON `l0`.`Id` = `l2`.`Id`
- WHERE ((`l0`.`OneToOne_Required_PK_Date` IS NOT NULL AND (`l0`.`Level1_Required_Id` IS NOT NULL)) AND `l0`.`OneToMany_Required_Inverse2Id` IS NOT NULL) AND (`t`.`Id` = `l0`.`OneToMany_Required_Inverse2Id`)
+ FROM `Level1` AS `l1`
+ WHERE ((`l1`.`OneToOne_Required_PK_Date` IS NOT NULL AND (`l1`.`Level1_Required_Id` IS NOT NULL)) AND `l1`.`OneToMany_Required_Inverse2Id` IS NOT NULL) AND (`t`.`Id` = `l1`.`OneToMany_Required_Inverse2Id`)
ORDER BY CASE
- WHEN (`l0`.`OneToOne_Required_PK_Date` IS NOT NULL AND (`l0`.`Level1_Required_Id` IS NOT NULL)) AND `l0`.`OneToMany_Required_Inverse2Id` IS NOT NULL THEN `l0`.`Id`
+ WHEN (`l1`.`OneToOne_Required_PK_Date` IS NOT NULL AND (`l1`.`Level1_Required_Id` IS NOT NULL)) AND `l1`.`OneToMany_Required_Inverse2Id` IS NOT NULL THEN `l1`.`Id`
END
LIMIT 3
) AS `t1`
- INNER JOIN `Level1` AS `l1` ON `t1`.`Level1_Required_Id` = `l1`.`Id`
+ INNER JOIN `Level1` AS `l0` ON `t1`.`Level1_Required_Id` = `l0`.`Id`
) AS `t0` ON TRUE
-ORDER BY `t`.`Id`, `t0`.`c`, `t0`.`Id1`, `t0`.`Id00`");
+ORDER BY `t`.`Id`, `t0`.`c`, `t0`.`Id1`");
}
public override async Task Skip_Take_Select_collection_Skip_Take(bool async)
@@ -110,7 +100,7 @@ public override async Task Skip_Take_Select_collection_Skip_Take(bool async)
AssertSql(
@"@__p_0='1'
-SELECT `t`.`Id`, `t`.`Name`, `t0`.`Id`, `t0`.`Name`, `t0`.`Level1Id`, `t0`.`Level2Id`, `t0`.`Id0`, `t0`.`Date`, `t0`.`Name0`, `t0`.`Id1`, `t0`.`Id00`
+SELECT `t`.`Id`, `t`.`Name`, `t0`.`Id`, `t0`.`Name`, `t0`.`Level1Id`, `t0`.`Level2Id`, `t0`.`Id0`, `t0`.`Date`, `t0`.`Name0`, `t0`.`Id1`
FROM (
SELECT `l`.`Id`, `l`.`Name`
FROM `Level1` AS `l`
@@ -120,22 +110,21 @@ LIMIT @__p_0 OFFSET @__p_0
LEFT JOIN LATERAL (
SELECT CASE
WHEN (`t1`.`OneToOne_Required_PK_Date` IS NOT NULL AND (`t1`.`Level1_Required_Id` IS NOT NULL)) AND `t1`.`OneToMany_Required_Inverse2Id` IS NOT NULL THEN `t1`.`Id`
- END AS `Id`, `t1`.`Level2_Name` AS `Name`, `t1`.`OneToMany_Required_Inverse2Id` AS `Level1Id`, `t1`.`Level1_Required_Id` AS `Level2Id`, `l1`.`Id` AS `Id0`, `l1`.`Date`, `l1`.`Name` AS `Name0`, `t1`.`Id` AS `Id1`, `t1`.`Id0` AS `Id00`, `t1`.`c`
+ END AS `Id`, `t1`.`Level2_Name` AS `Name`, `t1`.`OneToMany_Required_Inverse2Id` AS `Level1Id`, `t1`.`Level1_Required_Id` AS `Level2Id`, `l0`.`Id` AS `Id0`, `l0`.`Date`, `l0`.`Name` AS `Name0`, `t1`.`Id` AS `Id1`, `t1`.`c`
FROM (
- SELECT `l0`.`Id`, `l0`.`OneToOne_Required_PK_Date`, `l0`.`Level1_Required_Id`, `l0`.`Level2_Name`, `l0`.`OneToMany_Required_Inverse2Id`, `l2`.`Id` AS `Id0`, CASE
- WHEN (`l0`.`OneToOne_Required_PK_Date` IS NOT NULL AND (`l0`.`Level1_Required_Id` IS NOT NULL)) AND `l0`.`OneToMany_Required_Inverse2Id` IS NOT NULL THEN `l0`.`Id`
+ SELECT `l1`.`Id`, `l1`.`OneToOne_Required_PK_Date`, `l1`.`Level1_Required_Id`, `l1`.`Level2_Name`, `l1`.`OneToMany_Required_Inverse2Id`, CASE
+ WHEN (`l1`.`OneToOne_Required_PK_Date` IS NOT NULL AND (`l1`.`Level1_Required_Id` IS NOT NULL)) AND `l1`.`OneToMany_Required_Inverse2Id` IS NOT NULL THEN `l1`.`Id`
END AS `c`
- FROM `Level1` AS `l0`
- INNER JOIN `Level1` AS `l2` ON `l0`.`Id` = `l2`.`Id`
- WHERE ((`l0`.`OneToOne_Required_PK_Date` IS NOT NULL AND (`l0`.`Level1_Required_Id` IS NOT NULL)) AND `l0`.`OneToMany_Required_Inverse2Id` IS NOT NULL) AND (`t`.`Id` = `l0`.`OneToMany_Required_Inverse2Id`)
+ FROM `Level1` AS `l1`
+ WHERE ((`l1`.`OneToOne_Required_PK_Date` IS NOT NULL AND (`l1`.`Level1_Required_Id` IS NOT NULL)) AND `l1`.`OneToMany_Required_Inverse2Id` IS NOT NULL) AND (`t`.`Id` = `l1`.`OneToMany_Required_Inverse2Id`)
ORDER BY CASE
- WHEN (`l0`.`OneToOne_Required_PK_Date` IS NOT NULL AND (`l0`.`Level1_Required_Id` IS NOT NULL)) AND `l0`.`OneToMany_Required_Inverse2Id` IS NOT NULL THEN `l0`.`Id`
+ WHEN (`l1`.`OneToOne_Required_PK_Date` IS NOT NULL AND (`l1`.`Level1_Required_Id` IS NOT NULL)) AND `l1`.`OneToMany_Required_Inverse2Id` IS NOT NULL THEN `l1`.`Id`
END
LIMIT 3 OFFSET 1
) AS `t1`
- INNER JOIN `Level1` AS `l1` ON `t1`.`Level1_Required_Id` = `l1`.`Id`
+ INNER JOIN `Level1` AS `l0` ON `t1`.`Level1_Required_Id` = `l0`.`Id`
) AS `t0` ON TRUE
-ORDER BY `t`.`Id`, `t0`.`c`, `t0`.`Id1`, `t0`.`Id00`");
+ORDER BY `t`.`Id`, `t0`.`c`, `t0`.`Id1`");
}
public override async Task Skip_Take_on_grouping_element_inside_collection_projection(bool async)
@@ -195,14 +184,14 @@ public override async Task Skip_Take_on_grouping_element_with_collection_include
await base.Skip_Take_on_grouping_element_with_collection_include(async);
AssertSql(
- @"SELECT `t`.`Date`, `t1`.`Id`, `t1`.`Date`, `t1`.`Name`, `t1`.`Id0`, `t1`.`OneToOne_Required_PK_Date`, `t1`.`Level1_Optional_Id`, `t1`.`Level1_Required_Id`, `t1`.`Level2_Name`, `t1`.`OneToMany_Optional_Inverse2Id`, `t1`.`OneToMany_Required_Inverse2Id`, `t1`.`OneToOne_Optional_PK_Inverse2Id`, `t1`.`Id00`
+ @"SELECT `t`.`Date`, `t1`.`Id`, `t1`.`Date`, `t1`.`Name`, `t1`.`Id0`, `t1`.`OneToOne_Required_PK_Date`, `t1`.`Level1_Optional_Id`, `t1`.`Level1_Required_Id`, `t1`.`Level2_Name`, `t1`.`OneToMany_Optional_Inverse2Id`, `t1`.`OneToMany_Required_Inverse2Id`, `t1`.`OneToOne_Optional_PK_Inverse2Id`
FROM (
SELECT `l`.`Date`
FROM `Level1` AS `l`
GROUP BY `l`.`Date`
) AS `t`
LEFT JOIN LATERAL (
- SELECT `t0`.`Id`, `t0`.`Date`, `t0`.`Name`, `t2`.`Id` AS `Id0`, `t2`.`OneToOne_Required_PK_Date`, `t2`.`Level1_Optional_Id`, `t2`.`Level1_Required_Id`, `t2`.`Level2_Name`, `t2`.`OneToMany_Optional_Inverse2Id`, `t2`.`OneToMany_Required_Inverse2Id`, `t2`.`OneToOne_Optional_PK_Inverse2Id`, `t2`.`Id0` AS `Id00`
+ SELECT `t0`.`Id`, `t0`.`Date`, `t0`.`Name`, `t2`.`Id` AS `Id0`, `t2`.`OneToOne_Required_PK_Date`, `t2`.`Level1_Optional_Id`, `t2`.`Level1_Required_Id`, `t2`.`Level2_Name`, `t2`.`OneToMany_Optional_Inverse2Id`, `t2`.`OneToMany_Required_Inverse2Id`, `t2`.`OneToOne_Optional_PK_Inverse2Id`
FROM (
SELECT `l0`.`Id`, `l0`.`Date`, `l0`.`Name`
FROM `Level1` AS `l0`
@@ -211,13 +200,12 @@ ORDER BY `l0`.`Name`
LIMIT 5 OFFSET 1
) AS `t0`
LEFT JOIN (
- SELECT `l1`.`Id`, `l1`.`OneToOne_Required_PK_Date`, `l1`.`Level1_Optional_Id`, `l1`.`Level1_Required_Id`, `l1`.`Level2_Name`, `l1`.`OneToMany_Optional_Inverse2Id`, `l1`.`OneToMany_Required_Inverse2Id`, `l1`.`OneToOne_Optional_PK_Inverse2Id`, `l2`.`Id` AS `Id0`
+ SELECT `l1`.`Id`, `l1`.`OneToOne_Required_PK_Date`, `l1`.`Level1_Optional_Id`, `l1`.`Level1_Required_Id`, `l1`.`Level2_Name`, `l1`.`OneToMany_Optional_Inverse2Id`, `l1`.`OneToMany_Required_Inverse2Id`, `l1`.`OneToOne_Optional_PK_Inverse2Id`
FROM `Level1` AS `l1`
- INNER JOIN `Level1` AS `l2` ON `l1`.`Id` = `l2`.`Id`
WHERE (`l1`.`OneToOne_Required_PK_Date` IS NOT NULL AND (`l1`.`Level1_Required_Id` IS NOT NULL)) AND `l1`.`OneToMany_Required_Inverse2Id` IS NOT NULL
) AS `t2` ON `t0`.`Id` = `t2`.`OneToMany_Optional_Inverse2Id`
) AS `t1` ON TRUE
-ORDER BY `t`.`Date`, `t1`.`Name`, `t1`.`Id`, `t1`.`Id0`");
+ORDER BY `t`.`Date`, `t1`.`Name`, `t1`.`Id`");
}
public override async Task Skip_Take_on_grouping_element_with_reference_include(bool async)
@@ -225,14 +213,14 @@ public override async Task Skip_Take_on_grouping_element_with_reference_include(
await base.Skip_Take_on_grouping_element_with_reference_include(async);
AssertSql(
- @"SELECT `t`.`Date`, `t1`.`Id`, `t1`.`Date`, `t1`.`Name`, `t1`.`Id0`, `t1`.`OneToOne_Required_PK_Date`, `t1`.`Level1_Optional_Id`, `t1`.`Level1_Required_Id`, `t1`.`Level2_Name`, `t1`.`OneToMany_Optional_Inverse2Id`, `t1`.`OneToMany_Required_Inverse2Id`, `t1`.`OneToOne_Optional_PK_Inverse2Id`, `t1`.`Id00`
+ @"SELECT `t`.`Date`, `t1`.`Id`, `t1`.`Date`, `t1`.`Name`, `t1`.`Id0`, `t1`.`OneToOne_Required_PK_Date`, `t1`.`Level1_Optional_Id`, `t1`.`Level1_Required_Id`, `t1`.`Level2_Name`, `t1`.`OneToMany_Optional_Inverse2Id`, `t1`.`OneToMany_Required_Inverse2Id`, `t1`.`OneToOne_Optional_PK_Inverse2Id`
FROM (
SELECT `l`.`Date`
FROM `Level1` AS `l`
GROUP BY `l`.`Date`
) AS `t`
LEFT JOIN LATERAL (
- SELECT `t0`.`Id`, `t0`.`Date`, `t0`.`Name`, `t2`.`Id` AS `Id0`, `t2`.`OneToOne_Required_PK_Date`, `t2`.`Level1_Optional_Id`, `t2`.`Level1_Required_Id`, `t2`.`Level2_Name`, `t2`.`OneToMany_Optional_Inverse2Id`, `t2`.`OneToMany_Required_Inverse2Id`, `t2`.`OneToOne_Optional_PK_Inverse2Id`, `t2`.`Id0` AS `Id00`
+ SELECT `t0`.`Id`, `t0`.`Date`, `t0`.`Name`, `t2`.`Id` AS `Id0`, `t2`.`OneToOne_Required_PK_Date`, `t2`.`Level1_Optional_Id`, `t2`.`Level1_Required_Id`, `t2`.`Level2_Name`, `t2`.`OneToMany_Optional_Inverse2Id`, `t2`.`OneToMany_Required_Inverse2Id`, `t2`.`OneToOne_Optional_PK_Inverse2Id`
FROM (
SELECT `l0`.`Id`, `l0`.`Date`, `l0`.`Name`
FROM `Level1` AS `l0`
@@ -241,13 +229,12 @@ ORDER BY `l0`.`Name`
LIMIT 5 OFFSET 1
) AS `t0`
LEFT JOIN (
- SELECT `l1`.`Id`, `l1`.`OneToOne_Required_PK_Date`, `l1`.`Level1_Optional_Id`, `l1`.`Level1_Required_Id`, `l1`.`Level2_Name`, `l1`.`OneToMany_Optional_Inverse2Id`, `l1`.`OneToMany_Required_Inverse2Id`, `l1`.`OneToOne_Optional_PK_Inverse2Id`, `l2`.`Id` AS `Id0`
+ SELECT `l1`.`Id`, `l1`.`OneToOne_Required_PK_Date`, `l1`.`Level1_Optional_Id`, `l1`.`Level1_Required_Id`, `l1`.`Level2_Name`, `l1`.`OneToMany_Optional_Inverse2Id`, `l1`.`OneToMany_Required_Inverse2Id`, `l1`.`OneToOne_Optional_PK_Inverse2Id`
FROM `Level1` AS `l1`
- INNER JOIN `Level1` AS `l2` ON `l1`.`Id` = `l2`.`Id`
WHERE (`l1`.`OneToOne_Required_PK_Date` IS NOT NULL AND (`l1`.`Level1_Required_Id` IS NOT NULL)) AND `l1`.`OneToMany_Required_Inverse2Id` IS NOT NULL
) AS `t2` ON `t0`.`Id` = `t2`.`Level1_Optional_Id`
) AS `t1` ON TRUE
-ORDER BY `t`.`Date`, `t1`.`Name`, `t1`.`Id`, `t1`.`Id0`");
+ORDER BY `t`.`Date`, `t1`.`Name`, `t1`.`Id`");
}
private void AssertSql(params string[] expected)
diff --git a/test/EFCore.MySql.FunctionalTests/Query/ComplexNavigationsCollectionsSplitQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/ComplexNavigationsCollectionsSplitQueryMySqlTest.cs
index 2f2b828dd..4267c5173 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/ComplexNavigationsCollectionsSplitQueryMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/ComplexNavigationsCollectionsSplitQueryMySqlTest.cs
@@ -44,13 +44,10 @@ public override async Task Filtered_include_OrderBy(bool async)
FROM `LevelOne` AS `l`
ORDER BY `l`.`Id`",
//
- @"SELECT `t`.`Id`, `t`.`Date`, `t`.`Level1_Optional_Id`, `t`.`Level1_Required_Id`, `t`.`Name`, `t`.`OneToMany_Optional_Inverse2Id`, `t`.`OneToMany_Optional_Self_Inverse2Id`, `t`.`OneToMany_Required_Inverse2Id`, `t`.`OneToMany_Required_Self_Inverse2Id`, `t`.`OneToOne_Optional_PK_Inverse2Id`, `t`.`OneToOne_Optional_Self2Id`, `l`.`Id`
+ @"SELECT `l0`.`Id`, `l0`.`Date`, `l0`.`Level1_Optional_Id`, `l0`.`Level1_Required_Id`, `l0`.`Name`, `l0`.`OneToMany_Optional_Inverse2Id`, `l0`.`OneToMany_Optional_Self_Inverse2Id`, `l0`.`OneToMany_Required_Inverse2Id`, `l0`.`OneToMany_Required_Self_Inverse2Id`, `l0`.`OneToOne_Optional_PK_Inverse2Id`, `l0`.`OneToOne_Optional_Self2Id`, `l`.`Id`
FROM `LevelOne` AS `l`
-INNER JOIN (
- SELECT `l0`.`Id`, `l0`.`Date`, `l0`.`Level1_Optional_Id`, `l0`.`Level1_Required_Id`, `l0`.`Name`, `l0`.`OneToMany_Optional_Inverse2Id`, `l0`.`OneToMany_Optional_Self_Inverse2Id`, `l0`.`OneToMany_Required_Inverse2Id`, `l0`.`OneToMany_Required_Self_Inverse2Id`, `l0`.`OneToOne_Optional_PK_Inverse2Id`, `l0`.`OneToOne_Optional_Self2Id`
- FROM `LevelTwo` AS `l0`
-) AS `t` ON `l`.`Id` = `t`.`OneToMany_Optional_Inverse2Id`
-ORDER BY `l`.`Id`, `t`.`Name`");
+INNER JOIN `LevelTwo` AS `l0` ON `l`.`Id` = `l0`.`OneToMany_Optional_Inverse2Id`
+ORDER BY `l`.`Id`, `l0`.`Name`");
}
public override async Task Filtered_ThenInclude_OrderBy(bool async)
@@ -67,14 +64,11 @@ public override async Task Filtered_ThenInclude_OrderBy(bool async)
INNER JOIN `LevelTwo` AS `l0` ON `l`.`Id` = `l0`.`OneToMany_Optional_Inverse2Id`
ORDER BY `l`.`Id`, `l0`.`Id`",
//
- @"SELECT `t`.`Id`, `t`.`Level2_Optional_Id`, `t`.`Level2_Required_Id`, `t`.`Name`, `t`.`OneToMany_Optional_Inverse3Id`, `t`.`OneToMany_Optional_Self_Inverse3Id`, `t`.`OneToMany_Required_Inverse3Id`, `t`.`OneToMany_Required_Self_Inverse3Id`, `t`.`OneToOne_Optional_PK_Inverse3Id`, `t`.`OneToOne_Optional_Self3Id`, `l`.`Id`, `l0`.`Id`
+ @"SELECT `l1`.`Id`, `l1`.`Level2_Optional_Id`, `l1`.`Level2_Required_Id`, `l1`.`Name`, `l1`.`OneToMany_Optional_Inverse3Id`, `l1`.`OneToMany_Optional_Self_Inverse3Id`, `l1`.`OneToMany_Required_Inverse3Id`, `l1`.`OneToMany_Required_Self_Inverse3Id`, `l1`.`OneToOne_Optional_PK_Inverse3Id`, `l1`.`OneToOne_Optional_Self3Id`, `l`.`Id`, `l0`.`Id`
FROM `LevelOne` AS `l`
INNER JOIN `LevelTwo` AS `l0` ON `l`.`Id` = `l0`.`OneToMany_Optional_Inverse2Id`
-INNER JOIN (
- SELECT `l1`.`Id`, `l1`.`Level2_Optional_Id`, `l1`.`Level2_Required_Id`, `l1`.`Name`, `l1`.`OneToMany_Optional_Inverse3Id`, `l1`.`OneToMany_Optional_Self_Inverse3Id`, `l1`.`OneToMany_Required_Inverse3Id`, `l1`.`OneToMany_Required_Self_Inverse3Id`, `l1`.`OneToOne_Optional_PK_Inverse3Id`, `l1`.`OneToOne_Optional_Self3Id`
- FROM `LevelThree` AS `l1`
-) AS `t` ON `l0`.`Id` = `t`.`OneToMany_Optional_Inverse3Id`
-ORDER BY `l`.`Id`, `l0`.`Id`, `t`.`Name`");
+INNER JOIN `LevelThree` AS `l1` ON `l0`.`Id` = `l1`.`OneToMany_Optional_Inverse3Id`
+ORDER BY `l`.`Id`, `l0`.`Id`, `l1`.`Name`");
}
public override async Task Filtered_include_ThenInclude_OrderBy(bool async)
@@ -86,25 +80,16 @@ public override async Task Filtered_include_ThenInclude_OrderBy(bool async)
FROM `LevelOne` AS `l`
ORDER BY `l`.`Id`",
//
- @"SELECT `t`.`Id`, `t`.`Date`, `t`.`Level1_Optional_Id`, `t`.`Level1_Required_Id`, `t`.`Name`, `t`.`OneToMany_Optional_Inverse2Id`, `t`.`OneToMany_Optional_Self_Inverse2Id`, `t`.`OneToMany_Required_Inverse2Id`, `t`.`OneToMany_Required_Self_Inverse2Id`, `t`.`OneToOne_Optional_PK_Inverse2Id`, `t`.`OneToOne_Optional_Self2Id`, `l`.`Id`
+ @"SELECT `l0`.`Id`, `l0`.`Date`, `l0`.`Level1_Optional_Id`, `l0`.`Level1_Required_Id`, `l0`.`Name`, `l0`.`OneToMany_Optional_Inverse2Id`, `l0`.`OneToMany_Optional_Self_Inverse2Id`, `l0`.`OneToMany_Required_Inverse2Id`, `l0`.`OneToMany_Required_Self_Inverse2Id`, `l0`.`OneToOne_Optional_PK_Inverse2Id`, `l0`.`OneToOne_Optional_Self2Id`, `l`.`Id`
FROM `LevelOne` AS `l`
-INNER JOIN (
- SELECT `l0`.`Id`, `l0`.`Date`, `l0`.`Level1_Optional_Id`, `l0`.`Level1_Required_Id`, `l0`.`Name`, `l0`.`OneToMany_Optional_Inverse2Id`, `l0`.`OneToMany_Optional_Self_Inverse2Id`, `l0`.`OneToMany_Required_Inverse2Id`, `l0`.`OneToMany_Required_Self_Inverse2Id`, `l0`.`OneToOne_Optional_PK_Inverse2Id`, `l0`.`OneToOne_Optional_Self2Id`
- FROM `LevelTwo` AS `l0`
-) AS `t` ON `l`.`Id` = `t`.`OneToMany_Optional_Inverse2Id`
-ORDER BY `l`.`Id`, `t`.`Name`, `t`.`Id`",
+INNER JOIN `LevelTwo` AS `l0` ON `l`.`Id` = `l0`.`OneToMany_Optional_Inverse2Id`
+ORDER BY `l`.`Id`, `l0`.`Name`, `l0`.`Id`",
//
- @"SELECT `t0`.`Id`, `t0`.`Level2_Optional_Id`, `t0`.`Level2_Required_Id`, `t0`.`Name`, `t0`.`OneToMany_Optional_Inverse3Id`, `t0`.`OneToMany_Optional_Self_Inverse3Id`, `t0`.`OneToMany_Required_Inverse3Id`, `t0`.`OneToMany_Required_Self_Inverse3Id`, `t0`.`OneToOne_Optional_PK_Inverse3Id`, `t0`.`OneToOne_Optional_Self3Id`, `l`.`Id`, `t`.`Id`
+ @"SELECT `l1`.`Id`, `l1`.`Level2_Optional_Id`, `l1`.`Level2_Required_Id`, `l1`.`Name`, `l1`.`OneToMany_Optional_Inverse3Id`, `l1`.`OneToMany_Optional_Self_Inverse3Id`, `l1`.`OneToMany_Required_Inverse3Id`, `l1`.`OneToMany_Required_Self_Inverse3Id`, `l1`.`OneToOne_Optional_PK_Inverse3Id`, `l1`.`OneToOne_Optional_Self3Id`, `l`.`Id`, `l0`.`Id`
FROM `LevelOne` AS `l`
-INNER JOIN (
- SELECT `l0`.`Id`, `l0`.`Name`, `l0`.`OneToMany_Optional_Inverse2Id`
- FROM `LevelTwo` AS `l0`
-) AS `t` ON `l`.`Id` = `t`.`OneToMany_Optional_Inverse2Id`
-INNER JOIN (
- SELECT `l1`.`Id`, `l1`.`Level2_Optional_Id`, `l1`.`Level2_Required_Id`, `l1`.`Name`, `l1`.`OneToMany_Optional_Inverse3Id`, `l1`.`OneToMany_Optional_Self_Inverse3Id`, `l1`.`OneToMany_Required_Inverse3Id`, `l1`.`OneToMany_Required_Self_Inverse3Id`, `l1`.`OneToOne_Optional_PK_Inverse3Id`, `l1`.`OneToOne_Optional_Self3Id`
- FROM `LevelThree` AS `l1`
-) AS `t0` ON `t`.`Id` = `t0`.`OneToMany_Optional_Inverse3Id`
-ORDER BY `l`.`Id`, `t`.`Name`, `t`.`Id`, `t0`.`Name` DESC");
+INNER JOIN `LevelTwo` AS `l0` ON `l`.`Id` = `l0`.`OneToMany_Optional_Inverse2Id`
+INNER JOIN `LevelThree` AS `l1` ON `l0`.`Id` = `l1`.`OneToMany_Optional_Inverse3Id`
+ORDER BY `l`.`Id`, `l0`.`Name`, `l0`.`Id`, `l1`.`Name` DESC");
}
public override async Task Filtered_include_basic_OrderBy_Take(bool async)
@@ -1073,7 +1058,7 @@ LEFT JOIN (
) AS `t0` ON `l`.`Id` = `t0`.`OneToMany_Optional_Inverse2Id`
ORDER BY `l`.`Id`, `t0`.`Id`",
//
- @"SELECT `t1`.`Id`, `l`.`Id`, `t0`.`Id`
+ @"SELECT `l1`.`Id`, `l`.`Id`, `t0`.`Id`
FROM `LevelOne` AS `l`
LEFT JOIN (
SELECT `t`.`Id`, `t`.`OneToMany_Optional_Inverse2Id`
@@ -1083,11 +1068,8 @@ LEFT JOIN (
) AS `t`
WHERE `t`.`row` <= 1
) AS `t0` ON `l`.`Id` = `t0`.`OneToMany_Optional_Inverse2Id`
-INNER JOIN (
- SELECT `l1`.`Id`, `l1`.`OneToMany_Optional_Inverse3Id`
- FROM `LevelThree` AS `l1`
-) AS `t1` ON `t0`.`Id` = `t1`.`OneToMany_Optional_Inverse3Id`
-ORDER BY `l`.`Id`, `t0`.`Id`, `t1`.`Id`");
+INNER JOIN `LevelThree` AS `l1` ON `t0`.`Id` = `l1`.`OneToMany_Optional_Inverse3Id`
+ORDER BY `l`.`Id`, `t0`.`Id`, `l1`.`Id`");
}
public override async Task Select_subquery_single_nested_subquery2(bool async)
@@ -1099,55 +1081,73 @@ public override async Task Select_subquery_single_nested_subquery2(bool async)
FROM `LevelOne` AS `l`
ORDER BY `l`.`Id`",
//
- @"SELECT `l`.`Id`, `t`.`Id`, `t0`.`Id`, `t0`.`c`
+ @"SELECT `l`.`Id`, `l0`.`Id`, `t0`.`Id`, `t0`.`c`
FROM `LevelOne` AS `l`
-INNER JOIN (
- SELECT `l0`.`Id`, `l0`.`OneToMany_Optional_Inverse2Id`
- FROM `LevelTwo` AS `l0`
-) AS `t` ON `l`.`Id` = `t`.`OneToMany_Optional_Inverse2Id`
+INNER JOIN `LevelTwo` AS `l0` ON `l`.`Id` = `l0`.`OneToMany_Optional_Inverse2Id`
LEFT JOIN (
- SELECT `t1`.`c`, `t1`.`Id`, `t1`.`OneToMany_Optional_Inverse3Id`
+ SELECT `t`.`c`, `t`.`Id`, `t`.`OneToMany_Optional_Inverse3Id`
FROM (
SELECT 1 AS `c`, `l1`.`Id`, `l1`.`OneToMany_Optional_Inverse3Id`, ROW_NUMBER() OVER(PARTITION BY `l1`.`OneToMany_Optional_Inverse3Id` ORDER BY `l1`.`Id`) AS `row`
FROM `LevelThree` AS `l1`
- ) AS `t1`
- WHERE `t1`.`row` <= 1
-) AS `t0` ON `t`.`Id` = `t0`.`OneToMany_Optional_Inverse3Id`
-ORDER BY `l`.`Id`, `t`.`Id`, `t0`.`Id`",
+ ) AS `t`
+ WHERE `t`.`row` <= 1
+) AS `t0` ON `l0`.`Id` = `t0`.`OneToMany_Optional_Inverse3Id`
+ORDER BY `l`.`Id`, `l0`.`Id`, `t0`.`Id`",
//
- @"SELECT `t2`.`Id`, `l`.`Id`, `t`.`Id`, `t0`.`Id`
+ @"SELECT `l2`.`Id`, `l`.`Id`, `l0`.`Id`, `t0`.`Id`
FROM `LevelOne` AS `l`
-INNER JOIN (
- SELECT `l0`.`Id`, `l0`.`OneToMany_Optional_Inverse2Id`
- FROM `LevelTwo` AS `l0`
-) AS `t` ON `l`.`Id` = `t`.`OneToMany_Optional_Inverse2Id`
+INNER JOIN `LevelTwo` AS `l0` ON `l`.`Id` = `l0`.`OneToMany_Optional_Inverse2Id`
LEFT JOIN (
- SELECT `t1`.`Id`, `t1`.`OneToMany_Optional_Inverse3Id`
+ SELECT `t`.`Id`, `t`.`OneToMany_Optional_Inverse3Id`
FROM (
SELECT `l1`.`Id`, `l1`.`OneToMany_Optional_Inverse3Id`, ROW_NUMBER() OVER(PARTITION BY `l1`.`OneToMany_Optional_Inverse3Id` ORDER BY `l1`.`Id`) AS `row`
FROM `LevelThree` AS `l1`
- ) AS `t1`
- WHERE `t1`.`row` <= 1
-) AS `t0` ON `t`.`Id` = `t0`.`OneToMany_Optional_Inverse3Id`
-INNER JOIN (
- SELECT `l2`.`Id`, `l2`.`OneToMany_Optional_Inverse4Id`
- FROM `LevelFour` AS `l2`
-) AS `t2` ON `t0`.`Id` = `t2`.`OneToMany_Optional_Inverse4Id`
-ORDER BY `l`.`Id`, `t`.`Id`, `t0`.`Id`, `t2`.`Id`");
+ ) AS `t`
+ WHERE `t`.`row` <= 1
+) AS `t0` ON `l0`.`Id` = `t0`.`OneToMany_Optional_Inverse3Id`
+INNER JOIN `LevelFour` AS `l2` ON `t0`.`Id` = `l2`.`OneToMany_Optional_Inverse4Id`
+ORDER BY `l`.`Id`, `l0`.`Id`, `t0`.`Id`, `l2`.`Id`");
}
public override async Task Queryable_in_subquery_works_when_final_projection_is_List(bool async)
{
await base.Queryable_in_subquery_works_when_final_projection_is_List(async);
- AssertSql(" ");
+ AssertSql();
}
public override async Task Complex_query_with_let_collection_projection_FirstOrDefault_with_ToList_on_inner_and_outer(bool async)
{
await base.Complex_query_with_let_collection_projection_FirstOrDefault_with_ToList_on_inner_and_outer(async);
- AssertSql(" ");
+ AssertSql(
+ @"SELECT `l`.`Id`, `t`.`Id`, `t`.`c`
+FROM `LevelOne` AS `l`
+LEFT JOIN LATERAL (
+ SELECT 1 AS `c`, `l0`.`Id`
+ FROM `LevelTwo` AS `l0`
+ WHERE (`l0`.`Name` <> 'Foo') OR `l0`.`Name` IS NULL
+ LIMIT 1
+) AS `t` ON TRUE
+ORDER BY `l`.`Id`, `t`.`Id`",
+ //
+ @"SELECT `t0`.`Name`, `l`.`Id`, `t`.`Id`
+FROM `LevelOne` AS `l`
+LEFT JOIN LATERAL (
+ SELECT `l0`.`Id`
+ FROM `LevelTwo` AS `l0`
+ WHERE (`l0`.`Name` <> 'Foo') OR `l0`.`Name` IS NULL
+ LIMIT 1
+) AS `t` ON TRUE
+JOIN LATERAL (
+ SELECT `l1`.`Name`
+ FROM `LevelOne` AS `l1`
+ WHERE EXISTS (
+ SELECT 1
+ FROM `LevelTwo` AS `l2`
+ WHERE (`l1`.`Id` = `l2`.`OneToMany_Optional_Inverse2Id`) AND (`l2`.`Id` = `t`.`Id`))
+) AS `t0` ON TRUE
+ORDER BY `l`.`Id`, `t`.`Id`");
}
public override async Task Complex_query_with_let_collection_projection_FirstOrDefault(bool async)
diff --git a/test/EFCore.MySql.FunctionalTests/Query/ComplexNavigationsQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/ComplexNavigationsQueryMySqlTest.cs
index 0b8d3ea70..866186922 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/ComplexNavigationsQueryMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/ComplexNavigationsQueryMySqlTest.cs
@@ -1,8 +1,15 @@
+using System;
+using System.Linq;
using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Query;
+using Microsoft.EntityFrameworkCore.TestModels.ComplexNavigationsModel;
using Pomelo.EntityFrameworkCore.MySql.Infrastructure;
+using Pomelo.EntityFrameworkCore.MySql.Tests;
using Pomelo.EntityFrameworkCore.MySql.Tests.TestUtilities.Attributes;
+using Xunit;
using Xunit.Abstractions;
+using Xunit.Sdk;
namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests.Query
{
@@ -23,5 +30,75 @@ public override Task Contains_with_subquery_optional_navigation_and_constant_ite
{
return base.Contains_with_subquery_optional_navigation_and_constant_item(async);
}
+
+ public override async Task SelectMany_subquery_with_custom_projection(bool async)
+ {
+ // TODO: Fix test in EF Core upstream.
+ // ORDER BY `l`.`Id`
+ // is ambiguous, since all 5 queried rows have an `Id` of `1`.
+ await AssertQuery(
+ async,
+ ss => ss.Set()/*.OrderBy(l1 => l1.Id)*/.SelectMany( // <-- has no effect anymore
+ l1 => l1.OneToMany_Optional1.Select(
+ l2 => new { l2.Name }))
+ .OrderBy(l0 => l0.Name) // <-- fix
+ .Take(1));
+
+ AssertSql(
+ @"@__p_0='1'
+
+SELECT `l0`.`Name`
+FROM `LevelOne` AS `l`
+INNER JOIN `LevelTwo` AS `l0` ON `l`.`Id` = `l0`.`OneToMany_Optional_Inverse2Id`
+ORDER BY `l0`.`Name`
+LIMIT @__p_0");
+ }
+
+ public override async Task GroupJoin_client_method_in_OrderBy(bool async)
+ {
+ await AssertTranslationFailedWithDetails(
+ () => base.GroupJoin_client_method_in_OrderBy(async),
+ CoreStrings.QueryUnableToTranslateMethod(
+ "Microsoft.EntityFrameworkCore.Query.ComplexNavigationsQueryTestBase",
+ "ClientMethodNullableInt"));
+
+ AssertSql();
+ }
+
+ public override async Task Join_with_result_selector_returning_queryable_throws_validation_error(bool async)
+ {
+ // Expression cannot be used for return type. Issue #23302.
+ await Assert.ThrowsAsync(
+ () => base.Join_with_result_selector_returning_queryable_throws_validation_error(async));
+
+ AssertSql();
+ }
+
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.OuterApply))]
+ public override async Task Nested_SelectMany_correlated_with_join_table_correctly_translated_to_apply(bool async)
+ {
+ // DefaultIfEmpty on child collection. Issue #19095.
+ await Assert.ThrowsAsync(
+ async () => await base.Nested_SelectMany_correlated_with_join_table_correctly_translated_to_apply(async));
+
+ AssertSql(
+ @"SELECT `t0`.`l1Name`, `t0`.`l2Name`, `t0`.`l3Name`
+FROM `LevelOne` AS `l`
+LEFT JOIN LATERAL (
+ SELECT `t`.`l1Name`, `t`.`l2Name`, `t`.`l3Name`
+ FROM `LevelTwo` AS `l0`
+ LEFT JOIN `LevelThree` AS `l1` ON `l0`.`Id` = `l1`.`Id`
+ JOIN LATERAL (
+ SELECT `l`.`Name` AS `l1Name`, `l1`.`Name` AS `l2Name`, `l3`.`Name` AS `l3Name`
+ FROM `LevelFour` AS `l2`
+ LEFT JOIN `LevelThree` AS `l3` ON `l2`.`OneToOne_Optional_PK_Inverse4Id` = `l3`.`Id`
+ WHERE `l1`.`Id` IS NOT NULL AND (`l1`.`Id` = `l2`.`OneToMany_Optional_Inverse4Id`)
+ ) AS `t` ON TRUE
+ WHERE `l`.`Id` = `l0`.`OneToMany_Optional_Inverse2Id`
+) AS `t0` ON TRUE");
+ }
+
+ private void AssertSql(params string[] expected)
+ => Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
}
}
diff --git a/test/EFCore.MySql.FunctionalTests/Query/ComplexNavigationsSharedTypeQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/ComplexNavigationsSharedTypeQueryMySqlTest.cs
index 39d64bd8c..31893354c 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/ComplexNavigationsSharedTypeQueryMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/ComplexNavigationsSharedTypeQueryMySqlTest.cs
@@ -1,12 +1,16 @@
-using System.Linq;
+using System;
+using System.Linq;
using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.TestModels.ComplexNavigationsModel;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Pomelo.EntityFrameworkCore.MySql.FunctionalTests.TestUtilities;
using Pomelo.EntityFrameworkCore.MySql.Infrastructure;
using Pomelo.EntityFrameworkCore.MySql.Tests.TestUtilities.Attributes;
+using Xunit;
using Xunit.Abstractions;
+using Xunit.Sdk;
namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests.Query
{
@@ -49,6 +53,68 @@ where l1.Id < 3
select l3).Distinct().OrderBy(e => e.Id).Skip(1).FirstOrDefault().Name); // Apply OrderBy before Skip
}
+ public override async Task SelectMany_subquery_with_custom_projection(bool async)
+ {
+ // TODO: Fix test in EF Core upstream.
+ // ORDER BY `l`.`Id`
+ // is ambiguous, since all 5 queried rows have an `Id` of `1`.
+ await AssertQuery(
+ async,
+ ss => ss.Set()/*.OrderBy(l1 => l1.Id)*/.SelectMany( // <-- has no effect anymore
+ l1 => l1.OneToMany_Optional1.Select(
+ l2 => new { l2.Name }))
+ .OrderBy(e => e.Name) // <-- fix
+ .Take(1));
+
+ AssertSql(
+ @"@__p_0='1'
+
+SELECT `t`.`Name`
+FROM `Level1` AS `l`
+INNER JOIN (
+ SELECT `l0`.`Level2_Name` AS `Name`, `l0`.`OneToMany_Optional_Inverse2Id`
+ FROM `Level1` AS `l0`
+ WHERE (`l0`.`OneToOne_Required_PK_Date` IS NOT NULL AND (`l0`.`Level1_Required_Id` IS NOT NULL)) AND `l0`.`OneToMany_Required_Inverse2Id` IS NOT NULL
+) AS `t` ON `l`.`Id` = `t`.`OneToMany_Optional_Inverse2Id`
+ORDER BY `t`.`Name`
+LIMIT @__p_0");
+ }
+
+ [ConditionalTheory(Skip = "https://github.com/dotnet/efcore/issues/26104")]
+ public override Task GroupBy_aggregate_where_required_relationship(bool async)
+ => base.GroupBy_aggregate_where_required_relationship(async);
+
+ [ConditionalTheory(Skip = "https://github.com/dotnet/efcore/issues/26104")]
+ public override Task GroupBy_aggregate_where_required_relationship_2(bool async)
+ => base.GroupBy_aggregate_where_required_relationship_2(async);
+
+ public override async Task GroupJoin_client_method_in_OrderBy(bool async)
+ {
+ await Assert.ThrowsAsync(
+ async () => await base.GroupJoin_client_method_in_OrderBy(async));
+
+ AssertSql();
+ }
+
+ public override async Task Join_with_result_selector_returning_queryable_throws_validation_error(bool async)
+ {
+ // Expression cannot be used for return type. Issue #23302.
+ await Assert.ThrowsAsync(
+ () => base.Join_with_result_selector_returning_queryable_throws_validation_error(async));
+
+ AssertSql();
+ }
+
+ [ConditionalTheory(Skip = "Does not throw an EqualException, but still does not work.")]
+ public override async Task Nested_SelectMany_correlated_with_join_table_correctly_translated_to_apply(bool async)
+ {
+ // DefaultIfEmpty on child collection. Issue #19095.
+ await Assert.ThrowsAsync(
+ async () => await base.Nested_SelectMany_correlated_with_join_table_correctly_translated_to_apply(async));
+
+ AssertSql();
+ }
+
private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
diff --git a/test/EFCore.MySql.FunctionalTests/Query/CompositeKeysQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/CompositeKeysQueryMySqlTest.cs
index 5cfcdddbb..02fbb5d28 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/CompositeKeysQueryMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/CompositeKeysQueryMySqlTest.cs
@@ -47,17 +47,11 @@ public override async Task Projecting_multiple_collections_with_ordering_same_le
await base.Projecting_multiple_collections_with_ordering_same_level(async);
AssertSql(
- @"SELECT `c`.`Name`, `c`.`Id1`, `c`.`Id2`, `t`.`Id1`, `t`.`Id2`, `t`.`Date`, `t`.`Level1_Optional_Id1`, `t`.`Level1_Optional_Id2`, `t`.`Level1_Required_Id1`, `t`.`Level1_Required_Id2`, `t`.`Name`, `t`.`OneToMany_Optional_Inverse2Id1`, `t`.`OneToMany_Optional_Inverse2Id2`, `t`.`OneToMany_Optional_Self_Inverse2Id1`, `t`.`OneToMany_Optional_Self_Inverse2Id2`, `t`.`OneToMany_Required_Inverse2Id1`, `t`.`OneToMany_Required_Inverse2Id2`, `t`.`OneToMany_Required_Self_Inverse2Id1`, `t`.`OneToMany_Required_Self_Inverse2Id2`, `t`.`OneToOne_Optional_PK_Inverse2Id1`, `t`.`OneToOne_Optional_PK_Inverse2Id2`, `t`.`OneToOne_Optional_Self2Id1`, `t`.`OneToOne_Optional_Self2Id2`, `t0`.`Id1`, `t0`.`Id2`, `t0`.`Date`, `t0`.`Level1_Optional_Id1`, `t0`.`Level1_Optional_Id2`, `t0`.`Level1_Required_Id1`, `t0`.`Level1_Required_Id2`, `t0`.`Name`, `t0`.`OneToMany_Optional_Inverse2Id1`, `t0`.`OneToMany_Optional_Inverse2Id2`, `t0`.`OneToMany_Optional_Self_Inverse2Id1`, `t0`.`OneToMany_Optional_Self_Inverse2Id2`, `t0`.`OneToMany_Required_Inverse2Id1`, `t0`.`OneToMany_Required_Inverse2Id2`, `t0`.`OneToMany_Required_Self_Inverse2Id1`, `t0`.`OneToMany_Required_Self_Inverse2Id2`, `t0`.`OneToOne_Optional_PK_Inverse2Id1`, `t0`.`OneToOne_Optional_PK_Inverse2Id2`, `t0`.`OneToOne_Optional_Self2Id1`, `t0`.`OneToOne_Optional_Self2Id2`
+ @"SELECT `c`.`Name`, `c`.`Id1`, `c`.`Id2`, `c0`.`Id1`, `c0`.`Id2`, `c0`.`Date`, `c0`.`Level1_Optional_Id1`, `c0`.`Level1_Optional_Id2`, `c0`.`Level1_Required_Id1`, `c0`.`Level1_Required_Id2`, `c0`.`Name`, `c0`.`OneToMany_Optional_Inverse2Id1`, `c0`.`OneToMany_Optional_Inverse2Id2`, `c0`.`OneToMany_Optional_Self_Inverse2Id1`, `c0`.`OneToMany_Optional_Self_Inverse2Id2`, `c0`.`OneToMany_Required_Inverse2Id1`, `c0`.`OneToMany_Required_Inverse2Id2`, `c0`.`OneToMany_Required_Self_Inverse2Id1`, `c0`.`OneToMany_Required_Self_Inverse2Id2`, `c0`.`OneToOne_Optional_PK_Inverse2Id1`, `c0`.`OneToOne_Optional_PK_Inverse2Id2`, `c0`.`OneToOne_Optional_Self2Id1`, `c0`.`OneToOne_Optional_Self2Id2`, `c1`.`Id1`, `c1`.`Id2`, `c1`.`Date`, `c1`.`Level1_Optional_Id1`, `c1`.`Level1_Optional_Id2`, `c1`.`Level1_Required_Id1`, `c1`.`Level1_Required_Id2`, `c1`.`Name`, `c1`.`OneToMany_Optional_Inverse2Id1`, `c1`.`OneToMany_Optional_Inverse2Id2`, `c1`.`OneToMany_Optional_Self_Inverse2Id1`, `c1`.`OneToMany_Optional_Self_Inverse2Id2`, `c1`.`OneToMany_Required_Inverse2Id1`, `c1`.`OneToMany_Required_Inverse2Id2`, `c1`.`OneToMany_Required_Self_Inverse2Id1`, `c1`.`OneToMany_Required_Self_Inverse2Id2`, `c1`.`OneToOne_Optional_PK_Inverse2Id1`, `c1`.`OneToOne_Optional_PK_Inverse2Id2`, `c1`.`OneToOne_Optional_Self2Id1`, `c1`.`OneToOne_Optional_Self2Id2`
FROM `CompositeOnes` AS `c`
-LEFT JOIN (
- SELECT `c0`.`Id1`, `c0`.`Id2`, `c0`.`Date`, `c0`.`Level1_Optional_Id1`, `c0`.`Level1_Optional_Id2`, `c0`.`Level1_Required_Id1`, `c0`.`Level1_Required_Id2`, `c0`.`Name`, `c0`.`OneToMany_Optional_Inverse2Id1`, `c0`.`OneToMany_Optional_Inverse2Id2`, `c0`.`OneToMany_Optional_Self_Inverse2Id1`, `c0`.`OneToMany_Optional_Self_Inverse2Id2`, `c0`.`OneToMany_Required_Inverse2Id1`, `c0`.`OneToMany_Required_Inverse2Id2`, `c0`.`OneToMany_Required_Self_Inverse2Id1`, `c0`.`OneToMany_Required_Self_Inverse2Id2`, `c0`.`OneToOne_Optional_PK_Inverse2Id1`, `c0`.`OneToOne_Optional_PK_Inverse2Id2`, `c0`.`OneToOne_Optional_Self2Id1`, `c0`.`OneToOne_Optional_Self2Id2`
- FROM `CompositeTwos` AS `c0`
-) AS `t` ON (`c`.`Id1` = `t`.`OneToMany_Optional_Inverse2Id1`) AND (`c`.`Id2` = `t`.`OneToMany_Optional_Inverse2Id2`)
-LEFT JOIN (
- SELECT `c1`.`Id1`, `c1`.`Id2`, `c1`.`Date`, `c1`.`Level1_Optional_Id1`, `c1`.`Level1_Optional_Id2`, `c1`.`Level1_Required_Id1`, `c1`.`Level1_Required_Id2`, `c1`.`Name`, `c1`.`OneToMany_Optional_Inverse2Id1`, `c1`.`OneToMany_Optional_Inverse2Id2`, `c1`.`OneToMany_Optional_Self_Inverse2Id1`, `c1`.`OneToMany_Optional_Self_Inverse2Id2`, `c1`.`OneToMany_Required_Inverse2Id1`, `c1`.`OneToMany_Required_Inverse2Id2`, `c1`.`OneToMany_Required_Self_Inverse2Id1`, `c1`.`OneToMany_Required_Self_Inverse2Id2`, `c1`.`OneToOne_Optional_PK_Inverse2Id1`, `c1`.`OneToOne_Optional_PK_Inverse2Id2`, `c1`.`OneToOne_Optional_Self2Id1`, `c1`.`OneToOne_Optional_Self2Id2`
- FROM `CompositeTwos` AS `c1`
-) AS `t0` ON (`c`.`Id1` = `t0`.`OneToMany_Required_Inverse2Id1`) AND (`c`.`Id2` = `t0`.`OneToMany_Required_Inverse2Id2`)
-ORDER BY `c`.`Id1`, `c`.`Id2`, `t`.`Id2`, `t`.`Id1`, `t0`.`Name` DESC, `t0`.`Id1`");
+LEFT JOIN `CompositeTwos` AS `c0` ON (`c`.`Id1` = `c0`.`OneToMany_Optional_Inverse2Id1`) AND (`c`.`Id2` = `c0`.`OneToMany_Optional_Inverse2Id2`)
+LEFT JOIN `CompositeTwos` AS `c1` ON (`c`.`Id1` = `c1`.`OneToMany_Required_Inverse2Id1`) AND (`c`.`Id2` = `c1`.`OneToMany_Required_Inverse2Id2`)
+ORDER BY `c`.`Id1`, `c`.`Id2`, `c0`.`Id2`, `c0`.`Id1`, `c1`.`Name` DESC, `c1`.`Id1`");
}
public override async Task Projecting_multiple_collections_with_ordering_same_level_top_level_ordering(bool async)
@@ -65,17 +59,11 @@ public override async Task Projecting_multiple_collections_with_ordering_same_le
await base.Projecting_multiple_collections_with_ordering_same_level_top_level_ordering(async);
AssertSql(
- @"SELECT `c`.`Name`, `c`.`Id1`, `c`.`Id2`, `t`.`Id1`, `t`.`Id2`, `t`.`Date`, `t`.`Level1_Optional_Id1`, `t`.`Level1_Optional_Id2`, `t`.`Level1_Required_Id1`, `t`.`Level1_Required_Id2`, `t`.`Name`, `t`.`OneToMany_Optional_Inverse2Id1`, `t`.`OneToMany_Optional_Inverse2Id2`, `t`.`OneToMany_Optional_Self_Inverse2Id1`, `t`.`OneToMany_Optional_Self_Inverse2Id2`, `t`.`OneToMany_Required_Inverse2Id1`, `t`.`OneToMany_Required_Inverse2Id2`, `t`.`OneToMany_Required_Self_Inverse2Id1`, `t`.`OneToMany_Required_Self_Inverse2Id2`, `t`.`OneToOne_Optional_PK_Inverse2Id1`, `t`.`OneToOne_Optional_PK_Inverse2Id2`, `t`.`OneToOne_Optional_Self2Id1`, `t`.`OneToOne_Optional_Self2Id2`, `t0`.`Id1`, `t0`.`Id2`, `t0`.`Date`, `t0`.`Level1_Optional_Id1`, `t0`.`Level1_Optional_Id2`, `t0`.`Level1_Required_Id1`, `t0`.`Level1_Required_Id2`, `t0`.`Name`, `t0`.`OneToMany_Optional_Inverse2Id1`, `t0`.`OneToMany_Optional_Inverse2Id2`, `t0`.`OneToMany_Optional_Self_Inverse2Id1`, `t0`.`OneToMany_Optional_Self_Inverse2Id2`, `t0`.`OneToMany_Required_Inverse2Id1`, `t0`.`OneToMany_Required_Inverse2Id2`, `t0`.`OneToMany_Required_Self_Inverse2Id1`, `t0`.`OneToMany_Required_Self_Inverse2Id2`, `t0`.`OneToOne_Optional_PK_Inverse2Id1`, `t0`.`OneToOne_Optional_PK_Inverse2Id2`, `t0`.`OneToOne_Optional_Self2Id1`, `t0`.`OneToOne_Optional_Self2Id2`
+ @"SELECT `c`.`Name`, `c`.`Id1`, `c`.`Id2`, `c0`.`Id1`, `c0`.`Id2`, `c0`.`Date`, `c0`.`Level1_Optional_Id1`, `c0`.`Level1_Optional_Id2`, `c0`.`Level1_Required_Id1`, `c0`.`Level1_Required_Id2`, `c0`.`Name`, `c0`.`OneToMany_Optional_Inverse2Id1`, `c0`.`OneToMany_Optional_Inverse2Id2`, `c0`.`OneToMany_Optional_Self_Inverse2Id1`, `c0`.`OneToMany_Optional_Self_Inverse2Id2`, `c0`.`OneToMany_Required_Inverse2Id1`, `c0`.`OneToMany_Required_Inverse2Id2`, `c0`.`OneToMany_Required_Self_Inverse2Id1`, `c0`.`OneToMany_Required_Self_Inverse2Id2`, `c0`.`OneToOne_Optional_PK_Inverse2Id1`, `c0`.`OneToOne_Optional_PK_Inverse2Id2`, `c0`.`OneToOne_Optional_Self2Id1`, `c0`.`OneToOne_Optional_Self2Id2`, `c1`.`Id1`, `c1`.`Id2`, `c1`.`Date`, `c1`.`Level1_Optional_Id1`, `c1`.`Level1_Optional_Id2`, `c1`.`Level1_Required_Id1`, `c1`.`Level1_Required_Id2`, `c1`.`Name`, `c1`.`OneToMany_Optional_Inverse2Id1`, `c1`.`OneToMany_Optional_Inverse2Id2`, `c1`.`OneToMany_Optional_Self_Inverse2Id1`, `c1`.`OneToMany_Optional_Self_Inverse2Id2`, `c1`.`OneToMany_Required_Inverse2Id1`, `c1`.`OneToMany_Required_Inverse2Id2`, `c1`.`OneToMany_Required_Self_Inverse2Id1`, `c1`.`OneToMany_Required_Self_Inverse2Id2`, `c1`.`OneToOne_Optional_PK_Inverse2Id1`, `c1`.`OneToOne_Optional_PK_Inverse2Id2`, `c1`.`OneToOne_Optional_Self2Id1`, `c1`.`OneToOne_Optional_Self2Id2`
FROM `CompositeOnes` AS `c`
-LEFT JOIN (
- SELECT `c0`.`Id1`, `c0`.`Id2`, `c0`.`Date`, `c0`.`Level1_Optional_Id1`, `c0`.`Level1_Optional_Id2`, `c0`.`Level1_Required_Id1`, `c0`.`Level1_Required_Id2`, `c0`.`Name`, `c0`.`OneToMany_Optional_Inverse2Id1`, `c0`.`OneToMany_Optional_Inverse2Id2`, `c0`.`OneToMany_Optional_Self_Inverse2Id1`, `c0`.`OneToMany_Optional_Self_Inverse2Id2`, `c0`.`OneToMany_Required_Inverse2Id1`, `c0`.`OneToMany_Required_Inverse2Id2`, `c0`.`OneToMany_Required_Self_Inverse2Id1`, `c0`.`OneToMany_Required_Self_Inverse2Id2`, `c0`.`OneToOne_Optional_PK_Inverse2Id1`, `c0`.`OneToOne_Optional_PK_Inverse2Id2`, `c0`.`OneToOne_Optional_Self2Id1`, `c0`.`OneToOne_Optional_Self2Id2`
- FROM `CompositeTwos` AS `c0`
-) AS `t` ON (`c`.`Id1` = `t`.`OneToMany_Optional_Inverse2Id1`) AND (`c`.`Id2` = `t`.`OneToMany_Optional_Inverse2Id2`)
-LEFT JOIN (
- SELECT `c1`.`Id1`, `c1`.`Id2`, `c1`.`Date`, `c1`.`Level1_Optional_Id1`, `c1`.`Level1_Optional_Id2`, `c1`.`Level1_Required_Id1`, `c1`.`Level1_Required_Id2`, `c1`.`Name`, `c1`.`OneToMany_Optional_Inverse2Id1`, `c1`.`OneToMany_Optional_Inverse2Id2`, `c1`.`OneToMany_Optional_Self_Inverse2Id1`, `c1`.`OneToMany_Optional_Self_Inverse2Id2`, `c1`.`OneToMany_Required_Inverse2Id1`, `c1`.`OneToMany_Required_Inverse2Id2`, `c1`.`OneToMany_Required_Self_Inverse2Id1`, `c1`.`OneToMany_Required_Self_Inverse2Id2`, `c1`.`OneToOne_Optional_PK_Inverse2Id1`, `c1`.`OneToOne_Optional_PK_Inverse2Id2`, `c1`.`OneToOne_Optional_Self2Id1`, `c1`.`OneToOne_Optional_Self2Id2`
- FROM `CompositeTwos` AS `c1`
-) AS `t0` ON (`c`.`Id1` = `t0`.`OneToMany_Required_Inverse2Id1`) AND (`c`.`Id2` = `t0`.`OneToMany_Required_Inverse2Id2`)
-ORDER BY `c`.`Id2`, `c`.`Id1`, `t`.`Id2`, `t`.`Id1`, `t0`.`Name` DESC, `t0`.`Id1`");
+LEFT JOIN `CompositeTwos` AS `c0` ON (`c`.`Id1` = `c0`.`OneToMany_Optional_Inverse2Id1`) AND (`c`.`Id2` = `c0`.`OneToMany_Optional_Inverse2Id2`)
+LEFT JOIN `CompositeTwos` AS `c1` ON (`c`.`Id1` = `c1`.`OneToMany_Required_Inverse2Id1`) AND (`c`.`Id2` = `c1`.`OneToMany_Required_Inverse2Id2`)
+ORDER BY `c`.`Id2`, `c`.`Id1`, `c0`.`Id2`, `c0`.`Id1`, `c1`.`Name` DESC, `c1`.`Id1`");
}
public override async Task Projecting_collections_multi_level(bool async)
@@ -83,17 +71,14 @@ public override async Task Projecting_collections_multi_level(bool async)
await base.Projecting_collections_multi_level(async);
AssertSql(
- @"SELECT `c`.`Name`, `c`.`Id1`, `c`.`Id2`, `t0`.`Name`, `t0`.`Id1`, `t0`.`Id2`, `t0`.`Id10`, `t0`.`Id20`, `t0`.`Level2_Optional_Id1`, `t0`.`Level2_Optional_Id2`, `t0`.`Level2_Required_Id1`, `t0`.`Level2_Required_Id2`, `t0`.`Name0`, `t0`.`OneToMany_Optional_Inverse3Id1`, `t0`.`OneToMany_Optional_Inverse3Id2`, `t0`.`OneToMany_Optional_Self_Inverse3Id1`, `t0`.`OneToMany_Optional_Self_Inverse3Id2`, `t0`.`OneToMany_Required_Inverse3Id1`, `t0`.`OneToMany_Required_Inverse3Id2`, `t0`.`OneToMany_Required_Self_Inverse3Id1`, `t0`.`OneToMany_Required_Self_Inverse3Id2`, `t0`.`OneToOne_Optional_PK_Inverse3Id1`, `t0`.`OneToOne_Optional_PK_Inverse3Id2`, `t0`.`OneToOne_Optional_Self3Id1`, `t0`.`OneToOne_Optional_Self3Id2`
+ @"SELECT `c`.`Name`, `c`.`Id1`, `c`.`Id2`, `t`.`Name`, `t`.`Id1`, `t`.`Id2`, `t`.`Id10`, `t`.`Id20`, `t`.`Level2_Optional_Id1`, `t`.`Level2_Optional_Id2`, `t`.`Level2_Required_Id1`, `t`.`Level2_Required_Id2`, `t`.`Name0`, `t`.`OneToMany_Optional_Inverse3Id1`, `t`.`OneToMany_Optional_Inverse3Id2`, `t`.`OneToMany_Optional_Self_Inverse3Id1`, `t`.`OneToMany_Optional_Self_Inverse3Id2`, `t`.`OneToMany_Required_Inverse3Id1`, `t`.`OneToMany_Required_Inverse3Id2`, `t`.`OneToMany_Required_Self_Inverse3Id1`, `t`.`OneToMany_Required_Self_Inverse3Id2`, `t`.`OneToOne_Optional_PK_Inverse3Id1`, `t`.`OneToOne_Optional_PK_Inverse3Id2`, `t`.`OneToOne_Optional_Self3Id1`, `t`.`OneToOne_Optional_Self3Id2`
FROM `CompositeOnes` AS `c`
LEFT JOIN (
- SELECT `c0`.`Name`, `c0`.`Id1`, `c0`.`Id2`, `t`.`Id1` AS `Id10`, `t`.`Id2` AS `Id20`, `t`.`Level2_Optional_Id1`, `t`.`Level2_Optional_Id2`, `t`.`Level2_Required_Id1`, `t`.`Level2_Required_Id2`, `t`.`Name` AS `Name0`, `t`.`OneToMany_Optional_Inverse3Id1`, `t`.`OneToMany_Optional_Inverse3Id2`, `t`.`OneToMany_Optional_Self_Inverse3Id1`, `t`.`OneToMany_Optional_Self_Inverse3Id2`, `t`.`OneToMany_Required_Inverse3Id1`, `t`.`OneToMany_Required_Inverse3Id2`, `t`.`OneToMany_Required_Self_Inverse3Id1`, `t`.`OneToMany_Required_Self_Inverse3Id2`, `t`.`OneToOne_Optional_PK_Inverse3Id1`, `t`.`OneToOne_Optional_PK_Inverse3Id2`, `t`.`OneToOne_Optional_Self3Id1`, `t`.`OneToOne_Optional_Self3Id2`, `c0`.`OneToMany_Optional_Inverse2Id1`, `c0`.`OneToMany_Optional_Inverse2Id2`
+ SELECT `c0`.`Name`, `c0`.`Id1`, `c0`.`Id2`, `c1`.`Id1` AS `Id10`, `c1`.`Id2` AS `Id20`, `c1`.`Level2_Optional_Id1`, `c1`.`Level2_Optional_Id2`, `c1`.`Level2_Required_Id1`, `c1`.`Level2_Required_Id2`, `c1`.`Name` AS `Name0`, `c1`.`OneToMany_Optional_Inverse3Id1`, `c1`.`OneToMany_Optional_Inverse3Id2`, `c1`.`OneToMany_Optional_Self_Inverse3Id1`, `c1`.`OneToMany_Optional_Self_Inverse3Id2`, `c1`.`OneToMany_Required_Inverse3Id1`, `c1`.`OneToMany_Required_Inverse3Id2`, `c1`.`OneToMany_Required_Self_Inverse3Id1`, `c1`.`OneToMany_Required_Self_Inverse3Id2`, `c1`.`OneToOne_Optional_PK_Inverse3Id1`, `c1`.`OneToOne_Optional_PK_Inverse3Id2`, `c1`.`OneToOne_Optional_Self3Id1`, `c1`.`OneToOne_Optional_Self3Id2`, `c0`.`OneToMany_Optional_Inverse2Id1`, `c0`.`OneToMany_Optional_Inverse2Id2`
FROM `CompositeTwos` AS `c0`
- LEFT JOIN (
- SELECT `c1`.`Id1`, `c1`.`Id2`, `c1`.`Level2_Optional_Id1`, `c1`.`Level2_Optional_Id2`, `c1`.`Level2_Required_Id1`, `c1`.`Level2_Required_Id2`, `c1`.`Name`, `c1`.`OneToMany_Optional_Inverse3Id1`, `c1`.`OneToMany_Optional_Inverse3Id2`, `c1`.`OneToMany_Optional_Self_Inverse3Id1`, `c1`.`OneToMany_Optional_Self_Inverse3Id2`, `c1`.`OneToMany_Required_Inverse3Id1`, `c1`.`OneToMany_Required_Inverse3Id2`, `c1`.`OneToMany_Required_Self_Inverse3Id1`, `c1`.`OneToMany_Required_Self_Inverse3Id2`, `c1`.`OneToOne_Optional_PK_Inverse3Id1`, `c1`.`OneToOne_Optional_PK_Inverse3Id2`, `c1`.`OneToOne_Optional_Self3Id1`, `c1`.`OneToOne_Optional_Self3Id2`
- FROM `CompositeThrees` AS `c1`
- ) AS `t` ON (`c0`.`Id1` = `t`.`OneToMany_Required_Inverse3Id1`) AND (`c0`.`Id2` = `t`.`OneToMany_Required_Inverse3Id2`)
-) AS `t0` ON (`c`.`Id1` = `t0`.`OneToMany_Optional_Inverse2Id1`) AND (`c`.`Id2` = `t0`.`OneToMany_Optional_Inverse2Id2`)
-ORDER BY `c`.`Id2`, `c`.`Id1`, `t0`.`Id2`, `t0`.`Id1`, `t0`.`Id20` DESC");
+ LEFT JOIN `CompositeThrees` AS `c1` ON (`c0`.`Id1` = `c1`.`OneToMany_Required_Inverse3Id1`) AND (`c0`.`Id2` = `c1`.`OneToMany_Required_Inverse3Id2`)
+) AS `t` ON (`c`.`Id1` = `t`.`OneToMany_Optional_Inverse2Id1`) AND (`c`.`Id2` = `t`.`OneToMany_Optional_Inverse2Id2`)
+ORDER BY `c`.`Id2`, `c`.`Id1`, `t`.`Id2`, `t`.`Id1`, `t`.`Id20` DESC");
}
public override async Task Projecting_multiple_collections_on_multiple_levels_no_explicit_ordering(bool async)
@@ -171,13 +156,10 @@ LEFT JOIN (
LEFT JOIN `CompositeFours` AS `c10` ON (`c8`.`Id1` = `c10`.`OneToMany_Optional_Inverse4Id1`) AND (`c8`.`Id2` = `c10`.`OneToMany_Optional_Inverse4Id2`)
) AS `t3` ON (`c7`.`Id1` = `t3`.`OneToMany_Optional_Inverse3Id1`) AND (`c7`.`Id2` = `t3`.`OneToMany_Optional_Inverse3Id2`)
LEFT JOIN (
- SELECT `c11`.`Name`, `c11`.`Id1`, `c11`.`Id2`, `c12`.`Id1` AS `Id10`, `c12`.`Id2` AS `Id20`, `c12`.`Level3_Optional_Id1`, `c12`.`Level3_Optional_Id2`, `c12`.`Level3_Required_Id1`, `c12`.`Level3_Required_Id2`, `c12`.`Name` AS `Name0`, `c12`.`OneToMany_Optional_Inverse4Id1`, `c12`.`OneToMany_Optional_Inverse4Id2`, `c12`.`OneToMany_Optional_Self_Inverse4Id1`, `c12`.`OneToMany_Optional_Self_Inverse4Id2`, `c12`.`OneToMany_Required_Inverse4Id1`, `c12`.`OneToMany_Required_Inverse4Id2`, `c12`.`OneToMany_Required_Self_Inverse4Id1`, `c12`.`OneToMany_Required_Self_Inverse4Id2`, `c12`.`OneToOne_Optional_PK_Inverse4Id1`, `c12`.`OneToOne_Optional_PK_Inverse4Id2`, `c12`.`OneToOne_Optional_Self4Id1`, `c12`.`OneToOne_Optional_Self4Id2`, `t5`.`Id1` AS `Id11`, `t5`.`Id2` AS `Id21`, `t5`.`Level3_Optional_Id1` AS `Level3_Optional_Id10`, `t5`.`Level3_Optional_Id2` AS `Level3_Optional_Id20`, `t5`.`Level3_Required_Id1` AS `Level3_Required_Id10`, `t5`.`Level3_Required_Id2` AS `Level3_Required_Id20`, `t5`.`Name` AS `Name1`, `t5`.`OneToMany_Optional_Inverse4Id1` AS `OneToMany_Optional_Inverse4Id10`, `t5`.`OneToMany_Optional_Inverse4Id2` AS `OneToMany_Optional_Inverse4Id20`, `t5`.`OneToMany_Optional_Self_Inverse4Id1` AS `OneToMany_Optional_Self_Inverse4Id10`, `t5`.`OneToMany_Optional_Self_Inverse4Id2` AS `OneToMany_Optional_Self_Inverse4Id20`, `t5`.`OneToMany_Required_Inverse4Id1` AS `OneToMany_Required_Inverse4Id10`, `t5`.`OneToMany_Required_Inverse4Id2` AS `OneToMany_Required_Inverse4Id20`, `t5`.`OneToMany_Required_Self_Inverse4Id1` AS `OneToMany_Required_Self_Inverse4Id10`, `t5`.`OneToMany_Required_Self_Inverse4Id2` AS `OneToMany_Required_Self_Inverse4Id20`, `t5`.`OneToOne_Optional_PK_Inverse4Id1` AS `OneToOne_Optional_PK_Inverse4Id10`, `t5`.`OneToOne_Optional_PK_Inverse4Id2` AS `OneToOne_Optional_PK_Inverse4Id20`, `t5`.`OneToOne_Optional_Self4Id1` AS `OneToOne_Optional_Self4Id10`, `t5`.`OneToOne_Optional_Self4Id2` AS `OneToOne_Optional_Self4Id20`, `t5`.`c`, `c11`.`OneToMany_Optional_Inverse3Id1`, `c11`.`OneToMany_Optional_Inverse3Id2`
+ SELECT `c11`.`Name`, `c11`.`Id1`, `c11`.`Id2`, `c12`.`Id1` AS `Id10`, `c12`.`Id2` AS `Id20`, `c12`.`Level3_Optional_Id1`, `c12`.`Level3_Optional_Id2`, `c12`.`Level3_Required_Id1`, `c12`.`Level3_Required_Id2`, `c12`.`Name` AS `Name0`, `c12`.`OneToMany_Optional_Inverse4Id1`, `c12`.`OneToMany_Optional_Inverse4Id2`, `c12`.`OneToMany_Optional_Self_Inverse4Id1`, `c12`.`OneToMany_Optional_Self_Inverse4Id2`, `c12`.`OneToMany_Required_Inverse4Id1`, `c12`.`OneToMany_Required_Inverse4Id2`, `c12`.`OneToMany_Required_Self_Inverse4Id1`, `c12`.`OneToMany_Required_Self_Inverse4Id2`, `c12`.`OneToOne_Optional_PK_Inverse4Id1`, `c12`.`OneToOne_Optional_PK_Inverse4Id2`, `c12`.`OneToOne_Optional_Self4Id1`, `c12`.`OneToOne_Optional_Self4Id2`, `c13`.`Id1` AS `Id11`, `c13`.`Id2` AS `Id21`, `c13`.`Level3_Optional_Id1` AS `Level3_Optional_Id10`, `c13`.`Level3_Optional_Id2` AS `Level3_Optional_Id20`, `c13`.`Level3_Required_Id1` AS `Level3_Required_Id10`, `c13`.`Level3_Required_Id2` AS `Level3_Required_Id20`, `c13`.`Name` AS `Name1`, `c13`.`OneToMany_Optional_Inverse4Id1` AS `OneToMany_Optional_Inverse4Id10`, `c13`.`OneToMany_Optional_Inverse4Id2` AS `OneToMany_Optional_Inverse4Id20`, `c13`.`OneToMany_Optional_Self_Inverse4Id1` AS `OneToMany_Optional_Self_Inverse4Id10`, `c13`.`OneToMany_Optional_Self_Inverse4Id2` AS `OneToMany_Optional_Self_Inverse4Id20`, `c13`.`OneToMany_Required_Inverse4Id1` AS `OneToMany_Required_Inverse4Id10`, `c13`.`OneToMany_Required_Inverse4Id2` AS `OneToMany_Required_Inverse4Id20`, `c13`.`OneToMany_Required_Self_Inverse4Id1` AS `OneToMany_Required_Self_Inverse4Id10`, `c13`.`OneToMany_Required_Self_Inverse4Id2` AS `OneToMany_Required_Self_Inverse4Id20`, `c13`.`OneToOne_Optional_PK_Inverse4Id1` AS `OneToOne_Optional_PK_Inverse4Id10`, `c13`.`OneToOne_Optional_PK_Inverse4Id2` AS `OneToOne_Optional_PK_Inverse4Id20`, `c13`.`OneToOne_Optional_Self4Id1` AS `OneToOne_Optional_Self4Id10`, `c13`.`OneToOne_Optional_Self4Id2` AS `OneToOne_Optional_Self4Id20`, CONCAT(`c13`.`Id1`, CAST(`c13`.`Id2` AS char)) AS `c`, `c11`.`OneToMany_Optional_Inverse3Id1`, `c11`.`OneToMany_Optional_Inverse3Id2`
FROM `CompositeThrees` AS `c11`
LEFT JOIN `CompositeFours` AS `c12` ON (`c11`.`Id1` = `c12`.`OneToMany_Optional_Inverse4Id1`) AND (`c11`.`Id2` = `c12`.`OneToMany_Optional_Inverse4Id2`)
- LEFT JOIN (
- SELECT `c13`.`Id1`, `c13`.`Id2`, `c13`.`Level3_Optional_Id1`, `c13`.`Level3_Optional_Id2`, `c13`.`Level3_Required_Id1`, `c13`.`Level3_Required_Id2`, `c13`.`Name`, `c13`.`OneToMany_Optional_Inverse4Id1`, `c13`.`OneToMany_Optional_Inverse4Id2`, `c13`.`OneToMany_Optional_Self_Inverse4Id1`, `c13`.`OneToMany_Optional_Self_Inverse4Id2`, `c13`.`OneToMany_Required_Inverse4Id1`, `c13`.`OneToMany_Required_Inverse4Id2`, `c13`.`OneToMany_Required_Self_Inverse4Id1`, `c13`.`OneToMany_Required_Self_Inverse4Id2`, `c13`.`OneToOne_Optional_PK_Inverse4Id1`, `c13`.`OneToOne_Optional_PK_Inverse4Id2`, `c13`.`OneToOne_Optional_Self4Id1`, `c13`.`OneToOne_Optional_Self4Id2`, CONCAT(`c13`.`Id1`, CAST(`c13`.`Id2` AS char)) AS `c`
- FROM `CompositeFours` AS `c13`
- ) AS `t5` ON (`c11`.`Id1` = `t5`.`OneToMany_Required_Inverse4Id1`) AND (`c11`.`Id2` = `t5`.`OneToMany_Required_Inverse4Id2`)
+ LEFT JOIN `CompositeFours` AS `c13` ON (`c11`.`Id1` = `c13`.`OneToMany_Required_Inverse4Id1`) AND (`c11`.`Id2` = `c13`.`OneToMany_Required_Inverse4Id2`)
) AS `t4` ON (`c7`.`Id1` = `t4`.`OneToMany_Optional_Inverse3Id1`) AND (`c7`.`Id2` = `t4`.`OneToMany_Optional_Inverse3Id2`)
) AS `t2` ON (`c`.`Id1` = `t2`.`OneToMany_Required_Inverse2Id1`) AND (`c`.`Id2` = `t2`.`OneToMany_Required_Inverse2Id2`)
ORDER BY `c`.`Name`, `c`.`Id1`, `c`.`Id2`, `t1`.`Id1`, `t1`.`Id2`, `t1`.`Id20` DESC, `t1`.`Id10` DESC, `t1`.`Id100`, `t1`.`Id200`, `t1`.`Id11`, `t1`.`Id21`, `t1`.`Id12`, `t1`.`Id22`, `t1`.`Id101`, `t1`.`Id201`, `t1`.`Id110`, `t1`.`Id210`, `t2`.`c`, `t2`.`Id1`, `t2`.`Id2`, `t2`.`Id10`, `t2`.`Id20`, `t2`.`Id100`, `t2`.`Id200`, `t2`.`Id11`, `t2`.`Id21`, `t2`.`Id12`, `t2`.`Id22`, `t2`.`Id101`, `t2`.`Id201`, `t2`.`c0` DESC, `t2`.`Id110`");
diff --git a/test/EFCore.MySql.FunctionalTests/Query/CompositeKeysSplitQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/CompositeKeysSplitQueryMySqlTest.cs
index eb6ac802c..27da7e812 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/CompositeKeysSplitQueryMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/CompositeKeysSplitQueryMySqlTest.cs
@@ -27,25 +27,16 @@ public override async Task Projecting_collections_multi_level(bool async)
FROM `CompositeOnes` AS `c`
ORDER BY `c`.`Id2`, `c`.`Id1`",
//
- @"SELECT `t`.`Name`, `c`.`Id1`, `c`.`Id2`, `t`.`Id1`, `t`.`Id2`
-FROM `CompositeOnes` AS `c`
-INNER JOIN (
- SELECT `c0`.`Name`, `c0`.`Id1`, `c0`.`Id2`, `c0`.`OneToMany_Optional_Inverse2Id1`, `c0`.`OneToMany_Optional_Inverse2Id2`
- FROM `CompositeTwos` AS `c0`
-) AS `t` ON (`c`.`Id1` = `t`.`OneToMany_Optional_Inverse2Id1`) AND (`c`.`Id2` = `t`.`OneToMany_Optional_Inverse2Id2`)
-ORDER BY `c`.`Id2`, `c`.`Id1`, `t`.`Id2`, `t`.`Id1`",
- //
- @"SELECT `t0`.`Id1`, `t0`.`Id2`, `t0`.`Level2_Optional_Id1`, `t0`.`Level2_Optional_Id2`, `t0`.`Level2_Required_Id1`, `t0`.`Level2_Required_Id2`, `t0`.`Name`, `t0`.`OneToMany_Optional_Inverse3Id1`, `t0`.`OneToMany_Optional_Inverse3Id2`, `t0`.`OneToMany_Optional_Self_Inverse3Id1`, `t0`.`OneToMany_Optional_Self_Inverse3Id2`, `t0`.`OneToMany_Required_Inverse3Id1`, `t0`.`OneToMany_Required_Inverse3Id2`, `t0`.`OneToMany_Required_Self_Inverse3Id1`, `t0`.`OneToMany_Required_Self_Inverse3Id2`, `t0`.`OneToOne_Optional_PK_Inverse3Id1`, `t0`.`OneToOne_Optional_PK_Inverse3Id2`, `t0`.`OneToOne_Optional_Self3Id1`, `t0`.`OneToOne_Optional_Self3Id2`, `c`.`Id1`, `c`.`Id2`, `t`.`Id1`, `t`.`Id2`
-FROM `CompositeOnes` AS `c`
-INNER JOIN (
- SELECT `c0`.`Id1`, `c0`.`Id2`, `c0`.`OneToMany_Optional_Inverse2Id1`, `c0`.`OneToMany_Optional_Inverse2Id2`
- FROM `CompositeTwos` AS `c0`
-) AS `t` ON (`c`.`Id1` = `t`.`OneToMany_Optional_Inverse2Id1`) AND (`c`.`Id2` = `t`.`OneToMany_Optional_Inverse2Id2`)
-INNER JOIN (
- SELECT `c1`.`Id1`, `c1`.`Id2`, `c1`.`Level2_Optional_Id1`, `c1`.`Level2_Optional_Id2`, `c1`.`Level2_Required_Id1`, `c1`.`Level2_Required_Id2`, `c1`.`Name`, `c1`.`OneToMany_Optional_Inverse3Id1`, `c1`.`OneToMany_Optional_Inverse3Id2`, `c1`.`OneToMany_Optional_Self_Inverse3Id1`, `c1`.`OneToMany_Optional_Self_Inverse3Id2`, `c1`.`OneToMany_Required_Inverse3Id1`, `c1`.`OneToMany_Required_Inverse3Id2`, `c1`.`OneToMany_Required_Self_Inverse3Id1`, `c1`.`OneToMany_Required_Self_Inverse3Id2`, `c1`.`OneToOne_Optional_PK_Inverse3Id1`, `c1`.`OneToOne_Optional_PK_Inverse3Id2`, `c1`.`OneToOne_Optional_Self3Id1`, `c1`.`OneToOne_Optional_Self3Id2`
- FROM `CompositeThrees` AS `c1`
-) AS `t0` ON (`t`.`Id1` = `t0`.`OneToMany_Required_Inverse3Id1`) AND (`t`.`Id2` = `t0`.`OneToMany_Required_Inverse3Id2`)
-ORDER BY `c`.`Id2`, `c`.`Id1`, `t`.`Id2`, `t`.`Id1`, `t0`.`Id2` DESC");
+ @"SELECT `c0`.`Name`, `c`.`Id1`, `c`.`Id2`, `c0`.`Id1`, `c0`.`Id2`
+FROM `CompositeOnes` AS `c`
+INNER JOIN `CompositeTwos` AS `c0` ON (`c`.`Id1` = `c0`.`OneToMany_Optional_Inverse2Id1`) AND (`c`.`Id2` = `c0`.`OneToMany_Optional_Inverse2Id2`)
+ORDER BY `c`.`Id2`, `c`.`Id1`, `c0`.`Id2`, `c0`.`Id1`",
+ //
+ @"SELECT `c1`.`Id1`, `c1`.`Id2`, `c1`.`Level2_Optional_Id1`, `c1`.`Level2_Optional_Id2`, `c1`.`Level2_Required_Id1`, `c1`.`Level2_Required_Id2`, `c1`.`Name`, `c1`.`OneToMany_Optional_Inverse3Id1`, `c1`.`OneToMany_Optional_Inverse3Id2`, `c1`.`OneToMany_Optional_Self_Inverse3Id1`, `c1`.`OneToMany_Optional_Self_Inverse3Id2`, `c1`.`OneToMany_Required_Inverse3Id1`, `c1`.`OneToMany_Required_Inverse3Id2`, `c1`.`OneToMany_Required_Self_Inverse3Id1`, `c1`.`OneToMany_Required_Self_Inverse3Id2`, `c1`.`OneToOne_Optional_PK_Inverse3Id1`, `c1`.`OneToOne_Optional_PK_Inverse3Id2`, `c1`.`OneToOne_Optional_Self3Id1`, `c1`.`OneToOne_Optional_Self3Id2`, `c`.`Id1`, `c`.`Id2`, `c0`.`Id1`, `c0`.`Id2`
+FROM `CompositeOnes` AS `c`
+INNER JOIN `CompositeTwos` AS `c0` ON (`c`.`Id1` = `c0`.`OneToMany_Optional_Inverse2Id1`) AND (`c`.`Id2` = `c0`.`OneToMany_Optional_Inverse2Id2`)
+INNER JOIN `CompositeThrees` AS `c1` ON (`c0`.`Id1` = `c1`.`OneToMany_Required_Inverse3Id1`) AND (`c0`.`Id2` = `c1`.`OneToMany_Required_Inverse3Id2`)
+ORDER BY `c`.`Id2`, `c`.`Id1`, `c0`.`Id2`, `c0`.`Id1`, `c1`.`Id2` DESC");
}
public override async Task Projecting_multiple_collections_on_multiple_levels_no_explicit_ordering(bool async)
@@ -138,8 +129,8 @@ public override async Task Projecting_multiple_collections_on_multiple_levels_so
{
await base.Projecting_multiple_collections_on_multiple_levels_some_explicit_ordering(async);
- AssertSql(
- @"SELECT `c`.`Id1`, `c`.`Id2`
+ AssertSql(
+ @"SELECT `c`.`Id1`, `c`.`Id2`
FROM `CompositeOnes` AS `c`
ORDER BY `c`.`Name`, `c`.`Id1`, `c`.`Id2`",
//
@@ -148,14 +139,11 @@ public override async Task Projecting_multiple_collections_on_multiple_levels_so
INNER JOIN `CompositeTwos` AS `c0` ON (`c`.`Id1` = `c0`.`OneToMany_Optional_Inverse2Id1`) AND (`c`.`Id2` = `c0`.`OneToMany_Optional_Inverse2Id2`)
ORDER BY `c`.`Name`, `c`.`Id1`, `c`.`Id2`, `c0`.`Id1`, `c0`.`Id2`",
//
- @"SELECT `c`.`Id1`, `c`.`Id2`, `c0`.`Id1`, `c0`.`Id2`, `t`.`Id1`, `t`.`Id2`
+ @"SELECT `c`.`Id1`, `c`.`Id2`, `c0`.`Id1`, `c0`.`Id2`, `c1`.`Id1`, `c1`.`Id2`
FROM `CompositeOnes` AS `c`
INNER JOIN `CompositeTwos` AS `c0` ON (`c`.`Id1` = `c0`.`OneToMany_Optional_Inverse2Id1`) AND (`c`.`Id2` = `c0`.`OneToMany_Optional_Inverse2Id2`)
-INNER JOIN (
- SELECT `c1`.`Id1`, `c1`.`Id2`, `c1`.`OneToMany_Required_Inverse3Id1`, `c1`.`OneToMany_Required_Inverse3Id2`
- FROM `CompositeThrees` AS `c1`
-) AS `t` ON (`c0`.`Id1` = `t`.`OneToMany_Required_Inverse3Id1`) AND (`c0`.`Id2` = `t`.`OneToMany_Required_Inverse3Id2`)
-ORDER BY `c`.`Name`, `c`.`Id1`, `c`.`Id2`, `c0`.`Id1`, `c0`.`Id2`, `t`.`Id2` DESC, `t`.`Id1` DESC",
+INNER JOIN `CompositeThrees` AS `c1` ON (`c0`.`Id1` = `c1`.`OneToMany_Required_Inverse3Id1`) AND (`c0`.`Id2` = `c1`.`OneToMany_Required_Inverse3Id2`)
+ORDER BY `c`.`Name`, `c`.`Id1`, `c`.`Id2`, `c0`.`Id1`, `c0`.`Id2`, `c1`.`Id2` DESC, `c1`.`Id1` DESC",
//
@"SELECT `c1`.`Name`, `c`.`Id1`, `c`.`Id2`, `c0`.`Id1`, `c0`.`Id2`, `c1`.`Id1`, `c1`.`Id2`
FROM `CompositeOnes` AS `c`
@@ -177,74 +165,50 @@ INNER JOIN (
INNER JOIN `CompositeFours` AS `c2` ON (`c1`.`Id1` = `c2`.`OneToMany_Optional_Inverse4Id1`) AND (`c1`.`Id2` = `c2`.`OneToMany_Optional_Inverse4Id2`)
ORDER BY `c`.`Name`, `c`.`Id1`, `c`.`Id2`, `c0`.`Id1`, `c0`.`Id2`, `c1`.`Id1`, `c1`.`Id2`",
//
- @"SELECT `t`.`Name`, `c`.`Id1`, `c`.`Id2`, `t`.`Id1`, `t`.`Id2`
+ @"SELECT `c0`.`Name`, `c`.`Id1`, `c`.`Id2`, `c0`.`Id1`, `c0`.`Id2`
FROM `CompositeOnes` AS `c`
-INNER JOIN (
- SELECT `c0`.`Name`, `c0`.`Id1`, `c0`.`Id2`, CHAR_LENGTH(`c0`.`Name`) AS `c`, `c0`.`OneToMany_Required_Inverse2Id1`, `c0`.`OneToMany_Required_Inverse2Id2`
- FROM `CompositeTwos` AS `c0`
-) AS `t` ON (`c`.`Id1` = `t`.`OneToMany_Required_Inverse2Id1`) AND (`c`.`Id2` = `t`.`OneToMany_Required_Inverse2Id2`)
-ORDER BY `c`.`Name`, `c`.`Id1`, `c`.`Id2`, `t`.`c`, `t`.`Id1`, `t`.`Id2`",
+INNER JOIN `CompositeTwos` AS `c0` ON (`c`.`Id1` = `c0`.`OneToMany_Required_Inverse2Id1`) AND (`c`.`Id2` = `c0`.`OneToMany_Required_Inverse2Id2`)
+ORDER BY `c`.`Name`, `c`.`Id1`, `c`.`Id2`, CHAR_LENGTH(`c0`.`Name`), `c0`.`Id1`, `c0`.`Id2`",
//
- @"SELECT `c1`.`Name`, `c`.`Id1`, `c`.`Id2`, `t`.`Id1`, `t`.`Id2`, `c1`.`Id1`, `c1`.`Id2`
+ @"SELECT `c1`.`Name`, `c`.`Id1`, `c`.`Id2`, `c0`.`Id1`, `c0`.`Id2`, `c1`.`Id1`, `c1`.`Id2`
FROM `CompositeOnes` AS `c`
-INNER JOIN (
- SELECT `c0`.`Id1`, `c0`.`Id2`, CHAR_LENGTH(`c0`.`Name`) AS `c`, `c0`.`OneToMany_Required_Inverse2Id1`, `c0`.`OneToMany_Required_Inverse2Id2`
- FROM `CompositeTwos` AS `c0`
-) AS `t` ON (`c`.`Id1` = `t`.`OneToMany_Required_Inverse2Id1`) AND (`c`.`Id2` = `t`.`OneToMany_Required_Inverse2Id2`)
-INNER JOIN `CompositeThrees` AS `c1` ON (`t`.`Id1` = `c1`.`OneToMany_Optional_Inverse3Id1`) AND (`t`.`Id2` = `c1`.`OneToMany_Optional_Inverse3Id2`)
-ORDER BY `c`.`Name`, `c`.`Id1`, `c`.`Id2`, `t`.`c`, `t`.`Id1`, `t`.`Id2`, `c1`.`Id1`, `c1`.`Id2`",
+INNER JOIN `CompositeTwos` AS `c0` ON (`c`.`Id1` = `c0`.`OneToMany_Required_Inverse2Id1`) AND (`c`.`Id2` = `c0`.`OneToMany_Required_Inverse2Id2`)
+INNER JOIN `CompositeThrees` AS `c1` ON (`c0`.`Id1` = `c1`.`OneToMany_Optional_Inverse3Id1`) AND (`c0`.`Id2` = `c1`.`OneToMany_Optional_Inverse3Id2`)
+ORDER BY `c`.`Name`, `c`.`Id1`, `c`.`Id2`, CHAR_LENGTH(`c0`.`Name`), `c0`.`Id1`, `c0`.`Id2`, `c1`.`Id1`, `c1`.`Id2`",
//
- @"SELECT `c2`.`Id1`, `c2`.`Id2`, `c2`.`Level3_Optional_Id1`, `c2`.`Level3_Optional_Id2`, `c2`.`Level3_Required_Id1`, `c2`.`Level3_Required_Id2`, `c2`.`Name`, `c2`.`OneToMany_Optional_Inverse4Id1`, `c2`.`OneToMany_Optional_Inverse4Id2`, `c2`.`OneToMany_Optional_Self_Inverse4Id1`, `c2`.`OneToMany_Optional_Self_Inverse4Id2`, `c2`.`OneToMany_Required_Inverse4Id1`, `c2`.`OneToMany_Required_Inverse4Id2`, `c2`.`OneToMany_Required_Self_Inverse4Id1`, `c2`.`OneToMany_Required_Self_Inverse4Id2`, `c2`.`OneToOne_Optional_PK_Inverse4Id1`, `c2`.`OneToOne_Optional_PK_Inverse4Id2`, `c2`.`OneToOne_Optional_Self4Id1`, `c2`.`OneToOne_Optional_Self4Id2`, `c`.`Id1`, `c`.`Id2`, `t`.`Id1`, `t`.`Id2`, `c1`.`Id1`, `c1`.`Id2`
+ @"SELECT `c2`.`Id1`, `c2`.`Id2`, `c2`.`Level3_Optional_Id1`, `c2`.`Level3_Optional_Id2`, `c2`.`Level3_Required_Id1`, `c2`.`Level3_Required_Id2`, `c2`.`Name`, `c2`.`OneToMany_Optional_Inverse4Id1`, `c2`.`OneToMany_Optional_Inverse4Id2`, `c2`.`OneToMany_Optional_Self_Inverse4Id1`, `c2`.`OneToMany_Optional_Self_Inverse4Id2`, `c2`.`OneToMany_Required_Inverse4Id1`, `c2`.`OneToMany_Required_Inverse4Id2`, `c2`.`OneToMany_Required_Self_Inverse4Id1`, `c2`.`OneToMany_Required_Self_Inverse4Id2`, `c2`.`OneToOne_Optional_PK_Inverse4Id1`, `c2`.`OneToOne_Optional_PK_Inverse4Id2`, `c2`.`OneToOne_Optional_Self4Id1`, `c2`.`OneToOne_Optional_Self4Id2`, `c`.`Id1`, `c`.`Id2`, `c0`.`Id1`, `c0`.`Id2`, `c1`.`Id1`, `c1`.`Id2`
FROM `CompositeOnes` AS `c`
-INNER JOIN (
- SELECT `c0`.`Id1`, `c0`.`Id2`, CHAR_LENGTH(`c0`.`Name`) AS `c`, `c0`.`OneToMany_Required_Inverse2Id1`, `c0`.`OneToMany_Required_Inverse2Id2`
- FROM `CompositeTwos` AS `c0`
-) AS `t` ON (`c`.`Id1` = `t`.`OneToMany_Required_Inverse2Id1`) AND (`c`.`Id2` = `t`.`OneToMany_Required_Inverse2Id2`)
-INNER JOIN `CompositeThrees` AS `c1` ON (`t`.`Id1` = `c1`.`OneToMany_Optional_Inverse3Id1`) AND (`t`.`Id2` = `c1`.`OneToMany_Optional_Inverse3Id2`)
+INNER JOIN `CompositeTwos` AS `c0` ON (`c`.`Id1` = `c0`.`OneToMany_Required_Inverse2Id1`) AND (`c`.`Id2` = `c0`.`OneToMany_Required_Inverse2Id2`)
+INNER JOIN `CompositeThrees` AS `c1` ON (`c0`.`Id1` = `c1`.`OneToMany_Optional_Inverse3Id1`) AND (`c0`.`Id2` = `c1`.`OneToMany_Optional_Inverse3Id2`)
INNER JOIN `CompositeFours` AS `c2` ON (`c1`.`Id1` = `c2`.`OneToMany_Required_Inverse4Id1`) AND (`c1`.`Id2` = `c2`.`OneToMany_Required_Inverse4Id2`)
-ORDER BY `c`.`Name`, `c`.`Id1`, `c`.`Id2`, `t`.`c`, `t`.`Id1`, `t`.`Id2`, `c1`.`Id1`, `c1`.`Id2`",
+ORDER BY `c`.`Name`, `c`.`Id1`, `c`.`Id2`, CHAR_LENGTH(`c0`.`Name`), `c0`.`Id1`, `c0`.`Id2`, `c1`.`Id1`, `c1`.`Id2`",
//
- @"SELECT `c2`.`Id1`, `c2`.`Id2`, `c2`.`Level3_Optional_Id1`, `c2`.`Level3_Optional_Id2`, `c2`.`Level3_Required_Id1`, `c2`.`Level3_Required_Id2`, `c2`.`Name`, `c2`.`OneToMany_Optional_Inverse4Id1`, `c2`.`OneToMany_Optional_Inverse4Id2`, `c2`.`OneToMany_Optional_Self_Inverse4Id1`, `c2`.`OneToMany_Optional_Self_Inverse4Id2`, `c2`.`OneToMany_Required_Inverse4Id1`, `c2`.`OneToMany_Required_Inverse4Id2`, `c2`.`OneToMany_Required_Self_Inverse4Id1`, `c2`.`OneToMany_Required_Self_Inverse4Id2`, `c2`.`OneToOne_Optional_PK_Inverse4Id1`, `c2`.`OneToOne_Optional_PK_Inverse4Id2`, `c2`.`OneToOne_Optional_Self4Id1`, `c2`.`OneToOne_Optional_Self4Id2`, `c`.`Id1`, `c`.`Id2`, `t`.`Id1`, `t`.`Id2`, `c1`.`Id1`, `c1`.`Id2`
+ @"SELECT `c2`.`Id1`, `c2`.`Id2`, `c2`.`Level3_Optional_Id1`, `c2`.`Level3_Optional_Id2`, `c2`.`Level3_Required_Id1`, `c2`.`Level3_Required_Id2`, `c2`.`Name`, `c2`.`OneToMany_Optional_Inverse4Id1`, `c2`.`OneToMany_Optional_Inverse4Id2`, `c2`.`OneToMany_Optional_Self_Inverse4Id1`, `c2`.`OneToMany_Optional_Self_Inverse4Id2`, `c2`.`OneToMany_Required_Inverse4Id1`, `c2`.`OneToMany_Required_Inverse4Id2`, `c2`.`OneToMany_Required_Self_Inverse4Id1`, `c2`.`OneToMany_Required_Self_Inverse4Id2`, `c2`.`OneToOne_Optional_PK_Inverse4Id1`, `c2`.`OneToOne_Optional_PK_Inverse4Id2`, `c2`.`OneToOne_Optional_Self4Id1`, `c2`.`OneToOne_Optional_Self4Id2`, `c`.`Id1`, `c`.`Id2`, `c0`.`Id1`, `c0`.`Id2`, `c1`.`Id1`, `c1`.`Id2`
FROM `CompositeOnes` AS `c`
-INNER JOIN (
- SELECT `c0`.`Id1`, `c0`.`Id2`, CHAR_LENGTH(`c0`.`Name`) AS `c`, `c0`.`OneToMany_Required_Inverse2Id1`, `c0`.`OneToMany_Required_Inverse2Id2`
- FROM `CompositeTwos` AS `c0`
-) AS `t` ON (`c`.`Id1` = `t`.`OneToMany_Required_Inverse2Id1`) AND (`c`.`Id2` = `t`.`OneToMany_Required_Inverse2Id2`)
-INNER JOIN `CompositeThrees` AS `c1` ON (`t`.`Id1` = `c1`.`OneToMany_Optional_Inverse3Id1`) AND (`t`.`Id2` = `c1`.`OneToMany_Optional_Inverse3Id2`)
+INNER JOIN `CompositeTwos` AS `c0` ON (`c`.`Id1` = `c0`.`OneToMany_Required_Inverse2Id1`) AND (`c`.`Id2` = `c0`.`OneToMany_Required_Inverse2Id2`)
+INNER JOIN `CompositeThrees` AS `c1` ON (`c0`.`Id1` = `c1`.`OneToMany_Optional_Inverse3Id1`) AND (`c0`.`Id2` = `c1`.`OneToMany_Optional_Inverse3Id2`)
INNER JOIN `CompositeFours` AS `c2` ON (`c1`.`Id1` = `c2`.`OneToMany_Optional_Inverse4Id1`) AND (`c1`.`Id2` = `c2`.`OneToMany_Optional_Inverse4Id2`)
-ORDER BY `c`.`Name`, `c`.`Id1`, `c`.`Id2`, `t`.`c`, `t`.`Id1`, `t`.`Id2`, `c1`.`Id1`, `c1`.`Id2`",
+ORDER BY `c`.`Name`, `c`.`Id1`, `c`.`Id2`, CHAR_LENGTH(`c0`.`Name`), `c0`.`Id1`, `c0`.`Id2`, `c1`.`Id1`, `c1`.`Id2`",
//
- @"SELECT `c1`.`Name`, `c`.`Id1`, `c`.`Id2`, `t`.`Id1`, `t`.`Id2`, `c1`.`Id1`, `c1`.`Id2`
+ @"SELECT `c1`.`Name`, `c`.`Id1`, `c`.`Id2`, `c0`.`Id1`, `c0`.`Id2`, `c1`.`Id1`, `c1`.`Id2`
FROM `CompositeOnes` AS `c`
-INNER JOIN (
- SELECT `c0`.`Id1`, `c0`.`Id2`, CHAR_LENGTH(`c0`.`Name`) AS `c`, `c0`.`OneToMany_Required_Inverse2Id1`, `c0`.`OneToMany_Required_Inverse2Id2`
- FROM `CompositeTwos` AS `c0`
-) AS `t` ON (`c`.`Id1` = `t`.`OneToMany_Required_Inverse2Id1`) AND (`c`.`Id2` = `t`.`OneToMany_Required_Inverse2Id2`)
-INNER JOIN `CompositeThrees` AS `c1` ON (`t`.`Id1` = `c1`.`OneToMany_Optional_Inverse3Id1`) AND (`t`.`Id2` = `c1`.`OneToMany_Optional_Inverse3Id2`)
-ORDER BY `c`.`Name`, `c`.`Id1`, `c`.`Id2`, `t`.`c`, `t`.`Id1`, `t`.`Id2`, `c1`.`Id1`, `c1`.`Id2`",
+INNER JOIN `CompositeTwos` AS `c0` ON (`c`.`Id1` = `c0`.`OneToMany_Required_Inverse2Id1`) AND (`c`.`Id2` = `c0`.`OneToMany_Required_Inverse2Id2`)
+INNER JOIN `CompositeThrees` AS `c1` ON (`c0`.`Id1` = `c1`.`OneToMany_Optional_Inverse3Id1`) AND (`c0`.`Id2` = `c1`.`OneToMany_Optional_Inverse3Id2`)
+ORDER BY `c`.`Name`, `c`.`Id1`, `c`.`Id2`, CHAR_LENGTH(`c0`.`Name`), `c0`.`Id1`, `c0`.`Id2`, `c1`.`Id1`, `c1`.`Id2`",
//
- @"SELECT `c2`.`Id1`, `c2`.`Id2`, `c2`.`Level3_Optional_Id1`, `c2`.`Level3_Optional_Id2`, `c2`.`Level3_Required_Id1`, `c2`.`Level3_Required_Id2`, `c2`.`Name`, `c2`.`OneToMany_Optional_Inverse4Id1`, `c2`.`OneToMany_Optional_Inverse4Id2`, `c2`.`OneToMany_Optional_Self_Inverse4Id1`, `c2`.`OneToMany_Optional_Self_Inverse4Id2`, `c2`.`OneToMany_Required_Inverse4Id1`, `c2`.`OneToMany_Required_Inverse4Id2`, `c2`.`OneToMany_Required_Self_Inverse4Id1`, `c2`.`OneToMany_Required_Self_Inverse4Id2`, `c2`.`OneToOne_Optional_PK_Inverse4Id1`, `c2`.`OneToOne_Optional_PK_Inverse4Id2`, `c2`.`OneToOne_Optional_Self4Id1`, `c2`.`OneToOne_Optional_Self4Id2`, `c`.`Id1`, `c`.`Id2`, `t`.`Id1`, `t`.`Id2`, `c1`.`Id1`, `c1`.`Id2`
+ @"SELECT `c2`.`Id1`, `c2`.`Id2`, `c2`.`Level3_Optional_Id1`, `c2`.`Level3_Optional_Id2`, `c2`.`Level3_Required_Id1`, `c2`.`Level3_Required_Id2`, `c2`.`Name`, `c2`.`OneToMany_Optional_Inverse4Id1`, `c2`.`OneToMany_Optional_Inverse4Id2`, `c2`.`OneToMany_Optional_Self_Inverse4Id1`, `c2`.`OneToMany_Optional_Self_Inverse4Id2`, `c2`.`OneToMany_Required_Inverse4Id1`, `c2`.`OneToMany_Required_Inverse4Id2`, `c2`.`OneToMany_Required_Self_Inverse4Id1`, `c2`.`OneToMany_Required_Self_Inverse4Id2`, `c2`.`OneToOne_Optional_PK_Inverse4Id1`, `c2`.`OneToOne_Optional_PK_Inverse4Id2`, `c2`.`OneToOne_Optional_Self4Id1`, `c2`.`OneToOne_Optional_Self4Id2`, `c`.`Id1`, `c`.`Id2`, `c0`.`Id1`, `c0`.`Id2`, `c1`.`Id1`, `c1`.`Id2`
FROM `CompositeOnes` AS `c`
-INNER JOIN (
- SELECT `c0`.`Id1`, `c0`.`Id2`, CHAR_LENGTH(`c0`.`Name`) AS `c`, `c0`.`OneToMany_Required_Inverse2Id1`, `c0`.`OneToMany_Required_Inverse2Id2`
- FROM `CompositeTwos` AS `c0`
-) AS `t` ON (`c`.`Id1` = `t`.`OneToMany_Required_Inverse2Id1`) AND (`c`.`Id2` = `t`.`OneToMany_Required_Inverse2Id2`)
-INNER JOIN `CompositeThrees` AS `c1` ON (`t`.`Id1` = `c1`.`OneToMany_Optional_Inverse3Id1`) AND (`t`.`Id2` = `c1`.`OneToMany_Optional_Inverse3Id2`)
+INNER JOIN `CompositeTwos` AS `c0` ON (`c`.`Id1` = `c0`.`OneToMany_Required_Inverse2Id1`) AND (`c`.`Id2` = `c0`.`OneToMany_Required_Inverse2Id2`)
+INNER JOIN `CompositeThrees` AS `c1` ON (`c0`.`Id1` = `c1`.`OneToMany_Optional_Inverse3Id1`) AND (`c0`.`Id2` = `c1`.`OneToMany_Optional_Inverse3Id2`)
INNER JOIN `CompositeFours` AS `c2` ON (`c1`.`Id1` = `c2`.`OneToMany_Optional_Inverse4Id1`) AND (`c1`.`Id2` = `c2`.`OneToMany_Optional_Inverse4Id2`)
-ORDER BY `c`.`Name`, `c`.`Id1`, `c`.`Id2`, `t`.`c`, `t`.`Id1`, `t`.`Id2`, `c1`.`Id1`, `c1`.`Id2`",
- //
- @"SELECT `t0`.`Id1`, `t0`.`Id2`, `t0`.`Level3_Optional_Id1`, `t0`.`Level3_Optional_Id2`, `t0`.`Level3_Required_Id1`, `t0`.`Level3_Required_Id2`, `t0`.`Name`, `t0`.`OneToMany_Optional_Inverse4Id1`, `t0`.`OneToMany_Optional_Inverse4Id2`, `t0`.`OneToMany_Optional_Self_Inverse4Id1`, `t0`.`OneToMany_Optional_Self_Inverse4Id2`, `t0`.`OneToMany_Required_Inverse4Id1`, `t0`.`OneToMany_Required_Inverse4Id2`, `t0`.`OneToMany_Required_Self_Inverse4Id1`, `t0`.`OneToMany_Required_Self_Inverse4Id2`, `t0`.`OneToOne_Optional_PK_Inverse4Id1`, `t0`.`OneToOne_Optional_PK_Inverse4Id2`, `t0`.`OneToOne_Optional_Self4Id1`, `t0`.`OneToOne_Optional_Self4Id2`, `c`.`Id1`, `c`.`Id2`, `t`.`Id1`, `t`.`Id2`, `c1`.`Id1`, `c1`.`Id2`
-FROM `CompositeOnes` AS `c`
-INNER JOIN (
- SELECT `c0`.`Id1`, `c0`.`Id2`, CHAR_LENGTH(`c0`.`Name`) AS `c`, `c0`.`OneToMany_Required_Inverse2Id1`, `c0`.`OneToMany_Required_Inverse2Id2`
- FROM `CompositeTwos` AS `c0`
-) AS `t` ON (`c`.`Id1` = `t`.`OneToMany_Required_Inverse2Id1`) AND (`c`.`Id2` = `t`.`OneToMany_Required_Inverse2Id2`)
-INNER JOIN `CompositeThrees` AS `c1` ON (`t`.`Id1` = `c1`.`OneToMany_Optional_Inverse3Id1`) AND (`t`.`Id2` = `c1`.`OneToMany_Optional_Inverse3Id2`)
-INNER JOIN (
- SELECT `c2`.`Id1`, `c2`.`Id2`, `c2`.`Level3_Optional_Id1`, `c2`.`Level3_Optional_Id2`, `c2`.`Level3_Required_Id1`, `c2`.`Level3_Required_Id2`, `c2`.`Name`, `c2`.`OneToMany_Optional_Inverse4Id1`, `c2`.`OneToMany_Optional_Inverse4Id2`, `c2`.`OneToMany_Optional_Self_Inverse4Id1`, `c2`.`OneToMany_Optional_Self_Inverse4Id2`, `c2`.`OneToMany_Required_Inverse4Id1`, `c2`.`OneToMany_Required_Inverse4Id2`, `c2`.`OneToMany_Required_Self_Inverse4Id1`, `c2`.`OneToMany_Required_Self_Inverse4Id2`, `c2`.`OneToOne_Optional_PK_Inverse4Id1`, `c2`.`OneToOne_Optional_PK_Inverse4Id2`, `c2`.`OneToOne_Optional_Self4Id1`, `c2`.`OneToOne_Optional_Self4Id2`, CONCAT(`c2`.`Id1`, CAST(`c2`.`Id2` AS char)) AS `c`
- FROM `CompositeFours` AS `c2`
-) AS `t0` ON (`c1`.`Id1` = `t0`.`OneToMany_Required_Inverse4Id1`) AND (`c1`.`Id2` = `t0`.`OneToMany_Required_Inverse4Id2`)
-ORDER BY `c`.`Name`, `c`.`Id1`, `c`.`Id2`, `t`.`c`, `t`.`Id1`, `t`.`Id2`, `c1`.`Id1`, `c1`.`Id2`, `t0`.`c` DESC");
+ORDER BY `c`.`Name`, `c`.`Id1`, `c`.`Id2`, CHAR_LENGTH(`c0`.`Name`), `c0`.`Id1`, `c0`.`Id2`, `c1`.`Id1`, `c1`.`Id2`",
+ //
+ @"SELECT `c2`.`Id1`, `c2`.`Id2`, `c2`.`Level3_Optional_Id1`, `c2`.`Level3_Optional_Id2`, `c2`.`Level3_Required_Id1`, `c2`.`Level3_Required_Id2`, `c2`.`Name`, `c2`.`OneToMany_Optional_Inverse4Id1`, `c2`.`OneToMany_Optional_Inverse4Id2`, `c2`.`OneToMany_Optional_Self_Inverse4Id1`, `c2`.`OneToMany_Optional_Self_Inverse4Id2`, `c2`.`OneToMany_Required_Inverse4Id1`, `c2`.`OneToMany_Required_Inverse4Id2`, `c2`.`OneToMany_Required_Self_Inverse4Id1`, `c2`.`OneToMany_Required_Self_Inverse4Id2`, `c2`.`OneToOne_Optional_PK_Inverse4Id1`, `c2`.`OneToOne_Optional_PK_Inverse4Id2`, `c2`.`OneToOne_Optional_Self4Id1`, `c2`.`OneToOne_Optional_Self4Id2`, `c`.`Id1`, `c`.`Id2`, `c0`.`Id1`, `c0`.`Id2`, `c1`.`Id1`, `c1`.`Id2`
+FROM `CompositeOnes` AS `c`
+INNER JOIN `CompositeTwos` AS `c0` ON (`c`.`Id1` = `c0`.`OneToMany_Required_Inverse2Id1`) AND (`c`.`Id2` = `c0`.`OneToMany_Required_Inverse2Id2`)
+INNER JOIN `CompositeThrees` AS `c1` ON (`c0`.`Id1` = `c1`.`OneToMany_Optional_Inverse3Id1`) AND (`c0`.`Id2` = `c1`.`OneToMany_Optional_Inverse3Id2`)
+INNER JOIN `CompositeFours` AS `c2` ON (`c1`.`Id1` = `c2`.`OneToMany_Required_Inverse4Id1`) AND (`c1`.`Id2` = `c2`.`OneToMany_Required_Inverse4Id2`)
+ORDER BY `c`.`Name`, `c`.`Id1`, `c`.`Id2`, CHAR_LENGTH(`c0`.`Name`), `c0`.`Id1`, `c0`.`Id2`, `c1`.`Id1`, `c1`.`Id2`, CONCAT(`c2`.`Id1`, CAST(`c2`.`Id2` AS char)) DESC");
}
public override async Task Projecting_multiple_collections_same_level_top_level_ordering(bool async)
@@ -296,21 +260,15 @@ public override async Task Projecting_multiple_collections_with_ordering_same_le
FROM `CompositeOnes` AS `c`
ORDER BY `c`.`Id1`, `c`.`Id2`",
//
- @"SELECT `t`.`Id1`, `t`.`Id2`, `t`.`Date`, `t`.`Level1_Optional_Id1`, `t`.`Level1_Optional_Id2`, `t`.`Level1_Required_Id1`, `t`.`Level1_Required_Id2`, `t`.`Name`, `t`.`OneToMany_Optional_Inverse2Id1`, `t`.`OneToMany_Optional_Inverse2Id2`, `t`.`OneToMany_Optional_Self_Inverse2Id1`, `t`.`OneToMany_Optional_Self_Inverse2Id2`, `t`.`OneToMany_Required_Inverse2Id1`, `t`.`OneToMany_Required_Inverse2Id2`, `t`.`OneToMany_Required_Self_Inverse2Id1`, `t`.`OneToMany_Required_Self_Inverse2Id2`, `t`.`OneToOne_Optional_PK_Inverse2Id1`, `t`.`OneToOne_Optional_PK_Inverse2Id2`, `t`.`OneToOne_Optional_Self2Id1`, `t`.`OneToOne_Optional_Self2Id2`, `c`.`Id1`, `c`.`Id2`
+ @"SELECT `c0`.`Id1`, `c0`.`Id2`, `c0`.`Date`, `c0`.`Level1_Optional_Id1`, `c0`.`Level1_Optional_Id2`, `c0`.`Level1_Required_Id1`, `c0`.`Level1_Required_Id2`, `c0`.`Name`, `c0`.`OneToMany_Optional_Inverse2Id1`, `c0`.`OneToMany_Optional_Inverse2Id2`, `c0`.`OneToMany_Optional_Self_Inverse2Id1`, `c0`.`OneToMany_Optional_Self_Inverse2Id2`, `c0`.`OneToMany_Required_Inverse2Id1`, `c0`.`OneToMany_Required_Inverse2Id2`, `c0`.`OneToMany_Required_Self_Inverse2Id1`, `c0`.`OneToMany_Required_Self_Inverse2Id2`, `c0`.`OneToOne_Optional_PK_Inverse2Id1`, `c0`.`OneToOne_Optional_PK_Inverse2Id2`, `c0`.`OneToOne_Optional_Self2Id1`, `c0`.`OneToOne_Optional_Self2Id2`, `c`.`Id1`, `c`.`Id2`
FROM `CompositeOnes` AS `c`
-INNER JOIN (
- SELECT `c0`.`Id1`, `c0`.`Id2`, `c0`.`Date`, `c0`.`Level1_Optional_Id1`, `c0`.`Level1_Optional_Id2`, `c0`.`Level1_Required_Id1`, `c0`.`Level1_Required_Id2`, `c0`.`Name`, `c0`.`OneToMany_Optional_Inverse2Id1`, `c0`.`OneToMany_Optional_Inverse2Id2`, `c0`.`OneToMany_Optional_Self_Inverse2Id1`, `c0`.`OneToMany_Optional_Self_Inverse2Id2`, `c0`.`OneToMany_Required_Inverse2Id1`, `c0`.`OneToMany_Required_Inverse2Id2`, `c0`.`OneToMany_Required_Self_Inverse2Id1`, `c0`.`OneToMany_Required_Self_Inverse2Id2`, `c0`.`OneToOne_Optional_PK_Inverse2Id1`, `c0`.`OneToOne_Optional_PK_Inverse2Id2`, `c0`.`OneToOne_Optional_Self2Id1`, `c0`.`OneToOne_Optional_Self2Id2`
- FROM `CompositeTwos` AS `c0`
-) AS `t` ON (`c`.`Id1` = `t`.`OneToMany_Optional_Inverse2Id1`) AND (`c`.`Id2` = `t`.`OneToMany_Optional_Inverse2Id2`)
-ORDER BY `c`.`Id1`, `c`.`Id2`, `t`.`Id2`",
+INNER JOIN `CompositeTwos` AS `c0` ON (`c`.`Id1` = `c0`.`OneToMany_Optional_Inverse2Id1`) AND (`c`.`Id2` = `c0`.`OneToMany_Optional_Inverse2Id2`)
+ORDER BY `c`.`Id1`, `c`.`Id2`, `c0`.`Id2`",
//
- @"SELECT `t`.`Id1`, `t`.`Id2`, `t`.`Date`, `t`.`Level1_Optional_Id1`, `t`.`Level1_Optional_Id2`, `t`.`Level1_Required_Id1`, `t`.`Level1_Required_Id2`, `t`.`Name`, `t`.`OneToMany_Optional_Inverse2Id1`, `t`.`OneToMany_Optional_Inverse2Id2`, `t`.`OneToMany_Optional_Self_Inverse2Id1`, `t`.`OneToMany_Optional_Self_Inverse2Id2`, `t`.`OneToMany_Required_Inverse2Id1`, `t`.`OneToMany_Required_Inverse2Id2`, `t`.`OneToMany_Required_Self_Inverse2Id1`, `t`.`OneToMany_Required_Self_Inverse2Id2`, `t`.`OneToOne_Optional_PK_Inverse2Id1`, `t`.`OneToOne_Optional_PK_Inverse2Id2`, `t`.`OneToOne_Optional_Self2Id1`, `t`.`OneToOne_Optional_Self2Id2`, `c`.`Id1`, `c`.`Id2`
+ @"SELECT `c0`.`Id1`, `c0`.`Id2`, `c0`.`Date`, `c0`.`Level1_Optional_Id1`, `c0`.`Level1_Optional_Id2`, `c0`.`Level1_Required_Id1`, `c0`.`Level1_Required_Id2`, `c0`.`Name`, `c0`.`OneToMany_Optional_Inverse2Id1`, `c0`.`OneToMany_Optional_Inverse2Id2`, `c0`.`OneToMany_Optional_Self_Inverse2Id1`, `c0`.`OneToMany_Optional_Self_Inverse2Id2`, `c0`.`OneToMany_Required_Inverse2Id1`, `c0`.`OneToMany_Required_Inverse2Id2`, `c0`.`OneToMany_Required_Self_Inverse2Id1`, `c0`.`OneToMany_Required_Self_Inverse2Id2`, `c0`.`OneToOne_Optional_PK_Inverse2Id1`, `c0`.`OneToOne_Optional_PK_Inverse2Id2`, `c0`.`OneToOne_Optional_Self2Id1`, `c0`.`OneToOne_Optional_Self2Id2`, `c`.`Id1`, `c`.`Id2`
FROM `CompositeOnes` AS `c`
-INNER JOIN (
- SELECT `c0`.`Id1`, `c0`.`Id2`, `c0`.`Date`, `c0`.`Level1_Optional_Id1`, `c0`.`Level1_Optional_Id2`, `c0`.`Level1_Required_Id1`, `c0`.`Level1_Required_Id2`, `c0`.`Name`, `c0`.`OneToMany_Optional_Inverse2Id1`, `c0`.`OneToMany_Optional_Inverse2Id2`, `c0`.`OneToMany_Optional_Self_Inverse2Id1`, `c0`.`OneToMany_Optional_Self_Inverse2Id2`, `c0`.`OneToMany_Required_Inverse2Id1`, `c0`.`OneToMany_Required_Inverse2Id2`, `c0`.`OneToMany_Required_Self_Inverse2Id1`, `c0`.`OneToMany_Required_Self_Inverse2Id2`, `c0`.`OneToOne_Optional_PK_Inverse2Id1`, `c0`.`OneToOne_Optional_PK_Inverse2Id2`, `c0`.`OneToOne_Optional_Self2Id1`, `c0`.`OneToOne_Optional_Self2Id2`
- FROM `CompositeTwos` AS `c0`
-) AS `t` ON (`c`.`Id1` = `t`.`OneToMany_Required_Inverse2Id1`) AND (`c`.`Id2` = `t`.`OneToMany_Required_Inverse2Id2`)
-ORDER BY `c`.`Id1`, `c`.`Id2`, `t`.`Name` DESC");
+INNER JOIN `CompositeTwos` AS `c0` ON (`c`.`Id1` = `c0`.`OneToMany_Required_Inverse2Id1`) AND (`c`.`Id2` = `c0`.`OneToMany_Required_Inverse2Id2`)
+ORDER BY `c`.`Id1`, `c`.`Id2`, `c0`.`Name` DESC");
}
public override async Task Projecting_multiple_collections_with_ordering_same_level_top_level_ordering(bool async)
@@ -322,21 +280,15 @@ public override async Task Projecting_multiple_collections_with_ordering_same_le
FROM `CompositeOnes` AS `c`
ORDER BY `c`.`Id2`, `c`.`Id1`",
//
- @"SELECT `t`.`Id1`, `t`.`Id2`, `t`.`Date`, `t`.`Level1_Optional_Id1`, `t`.`Level1_Optional_Id2`, `t`.`Level1_Required_Id1`, `t`.`Level1_Required_Id2`, `t`.`Name`, `t`.`OneToMany_Optional_Inverse2Id1`, `t`.`OneToMany_Optional_Inverse2Id2`, `t`.`OneToMany_Optional_Self_Inverse2Id1`, `t`.`OneToMany_Optional_Self_Inverse2Id2`, `t`.`OneToMany_Required_Inverse2Id1`, `t`.`OneToMany_Required_Inverse2Id2`, `t`.`OneToMany_Required_Self_Inverse2Id1`, `t`.`OneToMany_Required_Self_Inverse2Id2`, `t`.`OneToOne_Optional_PK_Inverse2Id1`, `t`.`OneToOne_Optional_PK_Inverse2Id2`, `t`.`OneToOne_Optional_Self2Id1`, `t`.`OneToOne_Optional_Self2Id2`, `c`.`Id1`, `c`.`Id2`
+ @"SELECT `c0`.`Id1`, `c0`.`Id2`, `c0`.`Date`, `c0`.`Level1_Optional_Id1`, `c0`.`Level1_Optional_Id2`, `c0`.`Level1_Required_Id1`, `c0`.`Level1_Required_Id2`, `c0`.`Name`, `c0`.`OneToMany_Optional_Inverse2Id1`, `c0`.`OneToMany_Optional_Inverse2Id2`, `c0`.`OneToMany_Optional_Self_Inverse2Id1`, `c0`.`OneToMany_Optional_Self_Inverse2Id2`, `c0`.`OneToMany_Required_Inverse2Id1`, `c0`.`OneToMany_Required_Inverse2Id2`, `c0`.`OneToMany_Required_Self_Inverse2Id1`, `c0`.`OneToMany_Required_Self_Inverse2Id2`, `c0`.`OneToOne_Optional_PK_Inverse2Id1`, `c0`.`OneToOne_Optional_PK_Inverse2Id2`, `c0`.`OneToOne_Optional_Self2Id1`, `c0`.`OneToOne_Optional_Self2Id2`, `c`.`Id1`, `c`.`Id2`
FROM `CompositeOnes` AS `c`
-INNER JOIN (
- SELECT `c0`.`Id1`, `c0`.`Id2`, `c0`.`Date`, `c0`.`Level1_Optional_Id1`, `c0`.`Level1_Optional_Id2`, `c0`.`Level1_Required_Id1`, `c0`.`Level1_Required_Id2`, `c0`.`Name`, `c0`.`OneToMany_Optional_Inverse2Id1`, `c0`.`OneToMany_Optional_Inverse2Id2`, `c0`.`OneToMany_Optional_Self_Inverse2Id1`, `c0`.`OneToMany_Optional_Self_Inverse2Id2`, `c0`.`OneToMany_Required_Inverse2Id1`, `c0`.`OneToMany_Required_Inverse2Id2`, `c0`.`OneToMany_Required_Self_Inverse2Id1`, `c0`.`OneToMany_Required_Self_Inverse2Id2`, `c0`.`OneToOne_Optional_PK_Inverse2Id1`, `c0`.`OneToOne_Optional_PK_Inverse2Id2`, `c0`.`OneToOne_Optional_Self2Id1`, `c0`.`OneToOne_Optional_Self2Id2`
- FROM `CompositeTwos` AS `c0`
-) AS `t` ON (`c`.`Id1` = `t`.`OneToMany_Optional_Inverse2Id1`) AND (`c`.`Id2` = `t`.`OneToMany_Optional_Inverse2Id2`)
-ORDER BY `c`.`Id2`, `c`.`Id1`, `t`.`Id2`",
+INNER JOIN `CompositeTwos` AS `c0` ON (`c`.`Id1` = `c0`.`OneToMany_Optional_Inverse2Id1`) AND (`c`.`Id2` = `c0`.`OneToMany_Optional_Inverse2Id2`)
+ORDER BY `c`.`Id2`, `c`.`Id1`, `c0`.`Id2`",
//
- @"SELECT `t`.`Id1`, `t`.`Id2`, `t`.`Date`, `t`.`Level1_Optional_Id1`, `t`.`Level1_Optional_Id2`, `t`.`Level1_Required_Id1`, `t`.`Level1_Required_Id2`, `t`.`Name`, `t`.`OneToMany_Optional_Inverse2Id1`, `t`.`OneToMany_Optional_Inverse2Id2`, `t`.`OneToMany_Optional_Self_Inverse2Id1`, `t`.`OneToMany_Optional_Self_Inverse2Id2`, `t`.`OneToMany_Required_Inverse2Id1`, `t`.`OneToMany_Required_Inverse2Id2`, `t`.`OneToMany_Required_Self_Inverse2Id1`, `t`.`OneToMany_Required_Self_Inverse2Id2`, `t`.`OneToOne_Optional_PK_Inverse2Id1`, `t`.`OneToOne_Optional_PK_Inverse2Id2`, `t`.`OneToOne_Optional_Self2Id1`, `t`.`OneToOne_Optional_Self2Id2`, `c`.`Id1`, `c`.`Id2`
+ @"SELECT `c0`.`Id1`, `c0`.`Id2`, `c0`.`Date`, `c0`.`Level1_Optional_Id1`, `c0`.`Level1_Optional_Id2`, `c0`.`Level1_Required_Id1`, `c0`.`Level1_Required_Id2`, `c0`.`Name`, `c0`.`OneToMany_Optional_Inverse2Id1`, `c0`.`OneToMany_Optional_Inverse2Id2`, `c0`.`OneToMany_Optional_Self_Inverse2Id1`, `c0`.`OneToMany_Optional_Self_Inverse2Id2`, `c0`.`OneToMany_Required_Inverse2Id1`, `c0`.`OneToMany_Required_Inverse2Id2`, `c0`.`OneToMany_Required_Self_Inverse2Id1`, `c0`.`OneToMany_Required_Self_Inverse2Id2`, `c0`.`OneToOne_Optional_PK_Inverse2Id1`, `c0`.`OneToOne_Optional_PK_Inverse2Id2`, `c0`.`OneToOne_Optional_Self2Id1`, `c0`.`OneToOne_Optional_Self2Id2`, `c`.`Id1`, `c`.`Id2`
FROM `CompositeOnes` AS `c`
-INNER JOIN (
- SELECT `c0`.`Id1`, `c0`.`Id2`, `c0`.`Date`, `c0`.`Level1_Optional_Id1`, `c0`.`Level1_Optional_Id2`, `c0`.`Level1_Required_Id1`, `c0`.`Level1_Required_Id2`, `c0`.`Name`, `c0`.`OneToMany_Optional_Inverse2Id1`, `c0`.`OneToMany_Optional_Inverse2Id2`, `c0`.`OneToMany_Optional_Self_Inverse2Id1`, `c0`.`OneToMany_Optional_Self_Inverse2Id2`, `c0`.`OneToMany_Required_Inverse2Id1`, `c0`.`OneToMany_Required_Inverse2Id2`, `c0`.`OneToMany_Required_Self_Inverse2Id1`, `c0`.`OneToMany_Required_Self_Inverse2Id2`, `c0`.`OneToOne_Optional_PK_Inverse2Id1`, `c0`.`OneToOne_Optional_PK_Inverse2Id2`, `c0`.`OneToOne_Optional_Self2Id1`, `c0`.`OneToOne_Optional_Self2Id2`
- FROM `CompositeTwos` AS `c0`
-) AS `t` ON (`c`.`Id1` = `t`.`OneToMany_Required_Inverse2Id1`) AND (`c`.`Id2` = `t`.`OneToMany_Required_Inverse2Id2`)
-ORDER BY `c`.`Id2`, `c`.`Id1`, `t`.`Name` DESC");
+INNER JOIN `CompositeTwos` AS `c0` ON (`c`.`Id1` = `c0`.`OneToMany_Required_Inverse2Id1`) AND (`c`.`Id2` = `c0`.`OneToMany_Required_Inverse2Id2`)
+ORDER BY `c`.`Id2`, `c`.`Id1`, `c0`.`Name` DESC");
}
private void AssertSql(params string[] expected)
diff --git a/test/EFCore.MySql.FunctionalTests/Query/DateOnlyQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/DateOnlyQueryMySqlTest.cs
index 4815f2cfd..a0f957dd2 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/DateOnlyQueryMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/DateOnlyQueryMySqlTest.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
@@ -209,16 +210,15 @@ public Func GetContextCreator()
public ISetSource GetExpectedData()
=> new DateOnlyQueryData();
- public IReadOnlyDictionary GetEntitySorters()
+ public IReadOnlyDictionary EntitySorters
=> new Dictionary> { { typeof(Model.IceCream), e => ((Model.IceCream)e)?.IceCreamId }, }.ToDictionary(
- e => e.Key, e => (object)e.Value);
+ e => e.Key, e => (object)e.Value);
- public IReadOnlyDictionary GetEntityAsserters()
+ public IReadOnlyDictionary EntityAsserters
=> new Dictionary>
{
{
- typeof(Model.IceCream),
- (e, a) =>
+ typeof(Model.IceCream), (e, a) =>
{
Assert.Equal(e == null, a == null);
diff --git a/test/EFCore.MySql.FunctionalTests/Query/Ef6GroupByMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/Ef6GroupByMySqlTest.cs
new file mode 100644
index 000000000..1810b87fc
--- /dev/null
+++ b/test/EFCore.MySql.FunctionalTests/Query/Ef6GroupByMySqlTest.cs
@@ -0,0 +1,930 @@
+using System;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Query;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+using Pomelo.EntityFrameworkCore.MySql.FunctionalTests.TestUtilities;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests.Query;
+
+public class Ef6GroupByMySqlTest : Ef6GroupByTestBase
+{
+ public Ef6GroupByMySqlTest(Ef6GroupByMySqlFixture fixture, ITestOutputHelper testOutputHelper)
+ : base(fixture)
+ {
+ Fixture.TestSqlLoggerFactory.Clear();
+ // Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
+ }
+
+ [ConditionalFact]
+ public virtual void Check_all_tests_overridden()
+ => TestHelpers.AssertAllMethodsOverridden(GetType());
+
+ public override async Task GroupBy_is_optimized_when_projecting_group_key(bool async)
+ {
+ await base.GroupBy_is_optimized_when_projecting_group_key(async);
+
+ AssertSql(
+"""
+SELECT `a`.`FirstName`
+FROM `ArubaOwner` AS `a`
+GROUP BY `a`.`FirstName`
+""");
+
+ // EF6 SQL:
+ // @"SELECT
+ // [Distinct1].[FirstName] AS [FirstName]
+ // FROM ( SELECT DISTINCT
+ // [Extent1].[FirstName] AS [FirstName]
+ // FROM [dbo].[ArubaOwners] AS [Extent1]
+ // ) AS [Distinct1]";
+ }
+
+ public override async Task GroupBy_is_optimized_when_projecting_group_count(bool async)
+ {
+ await base.GroupBy_is_optimized_when_projecting_group_count(async);
+
+ AssertSql(
+"""
+SELECT COUNT(*)
+FROM `ArubaOwner` AS `a`
+GROUP BY `a`.`FirstName`
+""");
+
+ // EF6 SQL:
+ // @"SELECT
+ // [GroupBy1].[A1] AS [C1]
+ // FROM ( SELECT
+ // [Extent1].[FirstName] AS [K1],
+ // COUNT(1) AS [A1]
+ // FROM [dbo].[ArubaOwners] AS [Extent1]
+ // GROUP BY [Extent1].[FirstName]
+ // ) AS [GroupBy1]";
+ }
+
+ public override async Task GroupBy_is_optimized_when_projecting_expression_containing_group_key(bool async)
+ {
+ await base.GroupBy_is_optimized_when_projecting_expression_containing_group_key(async);
+
+ AssertSql(
+"""
+SELECT `a`.`Id` * 2
+FROM `ArubaOwner` AS `a`
+GROUP BY `a`.`Id`
+""");
+
+ // EF6 SQL:
+ // @"SELECT
+ // [Extent1].[Id] * 2 AS [C1]
+ // FROM [dbo].[ArubaOwners] AS [Extent1]";
+ }
+
+ public override async Task GroupBy_is_optimized_when_projecting_aggregate_on_the_group(bool async)
+ {
+ await base.GroupBy_is_optimized_when_projecting_aggregate_on_the_group(async);
+
+ AssertSql(
+"""
+SELECT MAX(`a`.`Id`)
+FROM `ArubaOwner` AS `a`
+GROUP BY `a`.`FirstName`
+""");
+
+ // EF6 SQL:
+ // @"SELECT
+ // [GroupBy1].[A1] AS [C1]
+ // FROM ( SELECT
+ // [Extent1].[FirstName] AS [K1],
+ // MAX([Extent1].[Id]) AS [A1]
+ // FROM [dbo].[ArubaOwners] AS [Extent1]
+ // GROUP BY [Extent1].[FirstName]
+ // ) AS [GroupBy1]";
+ }
+
+ public override async Task GroupBy_is_optimized_when_projecting_anonymous_type_containing_group_key_and_group_aggregate(bool async)
+ {
+ await base.GroupBy_is_optimized_when_projecting_anonymous_type_containing_group_key_and_group_aggregate(async);
+
+ AssertSql(
+"""
+SELECT `a`.`FirstName` AS `Key`, MAX(`a`.`Id`) AS `Aggregate`
+FROM `ArubaOwner` AS `a`
+GROUP BY `a`.`FirstName`
+""");
+
+ // EF6 SQL:
+ // @"SELECT
+ // 1 AS [C1],
+ // [GroupBy1].[K1] AS [FirstName],
+ // [GroupBy1].[A1] AS [C2]
+ // FROM ( SELECT
+ // [Extent1].[FirstName] AS [K1],
+ // MAX([Extent1].[Id]) AS [A1]
+ // FROM [dbo].[ArubaOwners] AS [Extent1]
+ // GROUP BY [Extent1].[FirstName]
+ // ) AS [GroupBy1]";
+ }
+
+ public override async Task GroupBy_is_optimized_when_projecting_anonymous_type_containing_group_key_and_multiple_group_aggregates(
+ bool async)
+ {
+ await base.GroupBy_is_optimized_when_projecting_anonymous_type_containing_group_key_and_multiple_group_aggregates(async);
+
+ AssertSql(
+"""
+SELECT `a`.`FirstName` AS `key1`, MAX(`a`.`Id`) AS `max`, MIN(`a`.`Id` + 2) AS `min`
+FROM `ArubaOwner` AS `a`
+GROUP BY `a`.`FirstName`
+""");
+
+ // EF6 SQL:
+ // @"SELECT
+ // 1 AS [C1],
+ // [GroupBy1].[K1] AS [FirstName],
+ // [GroupBy1].[A1] AS [C2],
+ // [GroupBy1].[A2] AS [C3]
+ // FROM ( SELECT
+ // [Extent1].[K1] AS [K1],
+ // MAX([Extent1].[A1_0]) AS [A1],
+ // MIN([Extent1].[A2_0]) AS [A2]
+ // FROM ( SELECT
+ // [Extent1].[FirstName] AS [K1],
+ // [Extent1].[Id] AS [A1_0],
+ // [Extent1].[Id] + 2 AS [A2_0]
+ // FROM [dbo].[ArubaOwners] AS [Extent1]
+ // ) AS [Extent1]
+ // GROUP BY [K1]
+ // ) AS [GroupBy1]";
+ }
+
+ public override async Task GroupBy_is_optimized_when_projecting_conditional_expression_containing_group_key(bool async)
+ {
+ await base.GroupBy_is_optimized_when_projecting_conditional_expression_containing_group_key(async);
+
+ AssertSql(
+"""
+@__p_0='False'
+
+SELECT CASE
+ WHEN `a`.`FirstName` IS NULL THEN 'is null'
+ ELSE 'not null'
+END AS `keyIsNull`, @__p_0 AS `logicExpression`
+FROM `ArubaOwner` AS `a`
+GROUP BY `a`.`FirstName`
+""");
+
+ // EF6 SQL:
+ // @"SELECT
+ // 1 AS [C1],
+ // CASE WHEN ([Distinct1].[FirstName] IS NULL) THEN N'is null' ELSE N'not null' END AS [C2],
+ // CASE WHEN (((@p__linq__0 = 1) AND (@p__linq__1 = 1)) OR ((@p__linq__2 = 1) AND (@p__linq__3 = 1))) THEN cast(1 as bit) WHEN ( NOT (((@p__linq__0 = 1) AND (@p__linq__1 = 1)) OR ((@p__linq__2 = 1) AND (@p__linq__3 = 1)))) THEN cast(0 as bit) END AS [C3]
+ // FROM ( SELECT DISTINCT
+ // [Extent1].[FirstName] AS [FirstName]
+ // FROM [dbo].[ArubaOwners] AS [Extent1]
+ // ) AS [Distinct1]";
+ }
+
+ public override async Task GroupBy_is_optimized_when_filerting_and_projecting_anonymous_type_with_group_key_and_function_aggregate(
+ bool async)
+ {
+ await base.GroupBy_is_optimized_when_filerting_and_projecting_anonymous_type_with_group_key_and_function_aggregate(async);
+
+ AssertSql(
+$"""
+SELECT `a`.`FirstName`, AVG({MySqlTestHelpers.CastAsDouble("`a`.`Id`")}) AS `AverageId`
+FROM `ArubaOwner` AS `a`
+WHERE `a`.`Id` > 5
+GROUP BY `a`.`FirstName`
+""");
+
+ // EF6 SQL:
+ // @"SELECT
+ // 1 AS [C1],
+ // [GroupBy1].[K1] AS [FirstName],
+ // [GroupBy1].[A1] AS [C2]
+ // FROM ( SELECT
+ // [Extent1].[FirstName] AS [K1],
+ // AVG( CAST( [Extent1].[Id] AS float)) AS [A1]
+ // FROM [dbo].[ArubaOwners] AS [Extent1]
+ // WHERE [Extent1].[Id] > 5
+ // GROUP BY [Extent1].[FirstName]
+ // ) AS [GroupBy1]";
+ }
+
+ public override async Task GroupBy_is_optimized_when_projecting_function_aggregate_with_expression(bool async)
+ {
+ await base.GroupBy_is_optimized_when_projecting_function_aggregate_with_expression(async);
+
+ AssertSql(
+"""
+SELECT MAX(`a`.`Id` * 2)
+FROM `ArubaOwner` AS `a`
+GROUP BY `a`.`FirstName`
+""");
+
+ // EF6 SQL:
+ // @"SELECT
+ // [GroupBy1].[A1] AS [C1]
+ // FROM ( SELECT
+ // [Extent1].[K1] AS [K1],
+ // MAX([Extent1].[A1_0]) AS [A1]
+ // FROM ( SELECT
+ // [Extent1].[FirstName] AS [K1],
+ // [Extent1].[Id] * 2 AS [A1_0]
+ // FROM [dbo].[ArubaOwners] AS [Extent1]
+ // ) AS [Extent1]
+ // GROUP BY [K1]
+ // ) AS [GroupBy1]";
+ }
+
+ public override async Task GroupBy_is_optimized_when_projecting_expression_with_multiple_function_aggregates(bool async)
+ {
+ await base.GroupBy_is_optimized_when_projecting_expression_with_multiple_function_aggregates(async);
+
+ AssertSql(
+"""
+SELECT MAX(`a`.`Id`) - MIN(`a`.`Id`) AS `maxMinusMin`
+FROM `ArubaOwner` AS `a`
+GROUP BY `a`.`FirstName`
+""");
+
+ // EF6 SQL:
+ // @"SELECT
+ // 1 AS [C1],
+ // [GroupBy1].[A1] - [GroupBy1].[A2] AS [C2]
+ // FROM ( SELECT
+ // [Extent1].[FirstName] AS [K1],
+ // MAX([Extent1].[Id]) AS [A1],
+ // MIN([Extent1].[Id]) AS [A2]
+ // FROM [dbo].[ArubaOwners] AS [Extent1]
+ // GROUP BY [Extent1].[FirstName]
+ // ) AS [GroupBy1]";
+ }
+
+ public override async Task GroupBy_is_optimized_when_grouping_by_row_and_projecting_column_of_the_key_row(bool async)
+ {
+ await base.GroupBy_is_optimized_when_grouping_by_row_and_projecting_column_of_the_key_row(async);
+
+ AssertSql(
+"""
+SELECT `a`.`FirstName`
+FROM `ArubaOwner` AS `a`
+WHERE `a`.`Id` < 4
+GROUP BY `a`.`FirstName`
+""");
+
+ // EF6 SQL:
+ // @"SELECT
+ // [Distinct1].[FirstName] AS [FirstName]
+ // FROM ( SELECT DISTINCT
+ // [Extent1].[FirstName] AS [FirstName]
+ // FROM [dbo].[ArubaOwners] AS [Extent1]
+ // WHERE [Extent1].[Id] < 4
+ // ) AS [Distinct1]";
+ }
+
+ public override async Task Grouping_by_all_columns_doesnt_produce_a_groupby_statement(bool async)
+ {
+ await base.Grouping_by_all_columns_doesnt_produce_a_groupby_statement(async);
+
+ AssertSql(
+"""
+SELECT `a`.`Id`, `a`.`Alias`, `a`.`FirstName`, `a`.`LastName`
+FROM `ArubaOwner` AS `a`
+GROUP BY `a`.`Id`, `a`.`Alias`, `a`.`FirstName`, `a`.`LastName`
+""");
+ }
+
+ public override async Task Grouping_by_all_columns_with_aggregate_function_works_1(bool async)
+ {
+ await base.Grouping_by_all_columns_with_aggregate_function_works_1(async);
+
+ AssertSql(
+"""
+SELECT COUNT(*)
+FROM `ArubaOwner` AS `a`
+GROUP BY `a`.`Id`, `a`.`FirstName`, `a`.`LastName`, `a`.`Alias`
+""");
+
+ // EF6 SQL:
+ // @"SELECT
+ // (SELECT
+ // COUNT(1) AS [A1]
+ // FROM [dbo].[ArubaOwners] AS [Extent2]
+ // WHERE [Extent1].[Id] = [Extent2].[Id]) AS [C1]
+ // FROM [dbo].[ArubaOwners] AS [Extent1]";
+ }
+
+ public override async Task Grouping_by_all_columns_with_aggregate_function_works_2(bool async)
+ {
+ await base.Grouping_by_all_columns_with_aggregate_function_works_2(async);
+
+ AssertSql(
+"""
+SELECT COUNT(*)
+FROM `ArubaOwner` AS `a`
+GROUP BY `a`.`Id`, `a`.`Alias`, `a`.`FirstName`, `a`.`LastName`
+""");
+ }
+
+ public override async Task Grouping_by_all_columns_with_aggregate_function_works_3(bool async)
+ {
+ await base.Grouping_by_all_columns_with_aggregate_function_works_3(async);
+
+ AssertSql(
+"""
+SELECT COUNT(*)
+FROM `ArubaOwner` AS `a`
+GROUP BY `a`.`Id`, `a`.`Alias`, `a`.`FirstName`, `a`.`LastName`
+""");
+ }
+
+ public override async Task Grouping_by_all_columns_with_aggregate_function_works_4(bool async)
+ {
+ await base.Grouping_by_all_columns_with_aggregate_function_works_4(async);
+
+ AssertSql(
+"""
+SELECT COUNT(*) AS `Count`
+FROM `ArubaOwner` AS `a`
+GROUP BY `a`.`Id`, `a`.`Alias`, `a`.`FirstName`, `a`.`LastName`
+""");
+ }
+
+ public override async Task Grouping_by_all_columns_with_aggregate_function_works_5(bool async)
+ {
+ await base.Grouping_by_all_columns_with_aggregate_function_works_5(async);
+
+ AssertSql(
+"""
+SELECT `a`.`Id`, COUNT(*) AS `Count`
+FROM `ArubaOwner` AS `a`
+GROUP BY `a`.`Id`, `a`.`Alias`, `a`.`FirstName`, `a`.`LastName`
+""");
+ }
+
+ public override async Task Grouping_by_all_columns_with_aggregate_function_works_6(bool async)
+ {
+ await base.Grouping_by_all_columns_with_aggregate_function_works_6(async);
+
+ AssertSql(
+"""
+SELECT `a`.`Id`, `a`.`Alias`, COUNT(*) AS `Count`
+FROM `ArubaOwner` AS `a`
+GROUP BY `a`.`Id`, `a`.`Alias`, `a`.`FirstName`, `a`.`LastName`
+""");
+ }
+
+ public override async Task Grouping_by_all_columns_with_aggregate_function_works_7(bool async)
+ {
+ await base.Grouping_by_all_columns_with_aggregate_function_works_7(async);
+
+ AssertSql(
+"""
+SELECT COUNT(*)
+FROM `ArubaOwner` AS `a`
+GROUP BY `a`.`Id`, `a`.`Alias`, `a`.`FirstName`, `a`.`LastName`
+""");
+ }
+
+ public override async Task Grouping_by_all_columns_with_aggregate_function_works_8(bool async)
+ {
+ await base.Grouping_by_all_columns_with_aggregate_function_works_8(async);
+
+ AssertSql(
+"""
+SELECT `a`.`Id`, COUNT(*) AS `Count`
+FROM `ArubaOwner` AS `a`
+GROUP BY `a`.`Id`, `a`.`Alias`, `a`.`FirstName`, `a`.`LastName`
+""");
+ }
+
+ public override async Task Grouping_by_all_columns_with_aggregate_function_works_9(bool async)
+ {
+ await base.Grouping_by_all_columns_with_aggregate_function_works_9(async);
+
+ AssertSql(
+"""
+SELECT `a`.`Id`, `a`.`Alias`, COUNT(*) AS `Count`
+FROM `ArubaOwner` AS `a`
+GROUP BY `a`.`Id`, `a`.`Alias`, `a`.`FirstName`, `a`.`LastName`
+""");
+ }
+
+ public override async Task Grouping_by_all_columns_with_aggregate_function_works_10(bool async)
+ {
+ await base.Grouping_by_all_columns_with_aggregate_function_works_10(async);
+
+ AssertSql(
+"""
+SELECT `a`.`Id`, COALESCE(SUM(`a`.`Id`), 0) AS `Sum`, COUNT(*) AS `Count`
+FROM `ArubaOwner` AS `a`
+GROUP BY `a`.`Id`, `a`.`Alias`, `a`.`FirstName`, `a`.`LastName`
+""");
+ }
+
+ public override async Task GroupBy_Simple_1_from_LINQ_101(bool async)
+ {
+ await base.GroupBy_Simple_1_from_LINQ_101(async);
+
+ AssertSql();
+ }
+
+ public override async Task GroupBy_Simple_2_from_LINQ_101(bool async)
+ {
+ await base.GroupBy_Simple_2_from_LINQ_101(async);
+
+ AssertSql();
+ }
+
+ public override async Task GroupBy_Simple_3_from_LINQ_101(bool async)
+ {
+ await base.GroupBy_Simple_3_from_LINQ_101(async);
+
+ AssertSql();
+ }
+
+ public override async Task GroupBy_Nested_from_LINQ_101(bool async)
+ {
+ await base.GroupBy_Nested_from_LINQ_101(async);
+
+ AssertSql();
+ }
+
+ public override async Task Any_Grouped_from_LINQ_101(bool async)
+ {
+ await base.Any_Grouped_from_LINQ_101(async);
+
+ AssertSql();
+ }
+
+ public override async Task All_Grouped_from_LINQ_101(bool async)
+ {
+ await base.All_Grouped_from_LINQ_101(async);
+
+ AssertSql();
+ }
+
+ public override async Task Min_Elements_from_LINQ_101(bool async)
+ {
+ await base.Min_Elements_from_LINQ_101(async);
+
+ AssertSql();
+ }
+
+ public override async Task Max_Elements_from_LINQ_101(bool async)
+ {
+ await base.Max_Elements_from_LINQ_101(async);
+
+ AssertSql();
+ }
+
+ public override async Task Group_Join_from_LINQ_101(bool async)
+ {
+ await base.Group_Join_from_LINQ_101(async);
+
+ AssertSql(
+"""
+SELECT `c`.`Id`, `c`.`CompanyName`, `c`.`Region`, `t`.`Id`, `t`.`CustomerId`, `t`.`OrderDate`, `t`.`Total`, `t`.`Id0`
+FROM `CustomerForLinq` AS `c`
+LEFT JOIN LATERAL (
+ SELECT `o`.`Id`, `o`.`CustomerId`, `o`.`OrderDate`, `o`.`Total`, `c0`.`Id` AS `Id0`
+ FROM `OrderForLinq` AS `o`
+ LEFT JOIN `CustomerForLinq` AS `c0` ON `o`.`CustomerId` = `c0`.`Id`
+ WHERE `c`.`Id` = `c0`.`Id`
+) AS `t` ON TRUE
+ORDER BY `c`.`Id`, `t`.`Id`
+""");
+ }
+
+ [ConditionalTheory(Skip = "Check why this does not throw in CI (MySQL 8.0.x), but does locally in the mysql:latest docker container.")]
+ public override async Task Whats_new_2021_sample_3(bool async)
+ {
+ // GroupBy debug assert. Issue #26104.
+ Assert.StartsWith(
+ "Missing alias in the list",
+ (await Assert.ThrowsAsync(
+ () => base.Whats_new_2021_sample_3(async))).Message);
+
+ AssertSql();
+ }
+
+ [ConditionalTheory(Skip = "Check why this does not throw in CI (MySQL 8.0.x), but does locally in the mysql:latest docker container.")]
+ public override async Task Whats_new_2021_sample_5(bool async)
+ {
+ await base.Whats_new_2021_sample_5(async);
+
+ AssertSql(
+ """
+SELECT (
+ SELECT p1."LastName"
+ FROM "Person" AS p1
+ WHERE p."FirstName" = p1."FirstName" OR ((p."FirstName" IS NULL) AND (p1."FirstName" IS NULL))
+ LIMIT 1)
+FROM "Person" AS p
+GROUP BY p."FirstName"
+ORDER BY (
+ SELECT p1."LastName"
+ FROM "Person" AS p1
+ WHERE p."FirstName" = p1."FirstName" OR ((p."FirstName" IS NULL) AND (p1."FirstName" IS NULL))
+ LIMIT 1) NULLS FIRST
+""");
+ }
+
+ [ConditionalTheory(Skip = "Check why this does not throw in CI (MySQL 8.0.x), but does locally in the mysql:latest docker container.")]
+ public override async Task Whats_new_2021_sample_6(bool async)
+ {
+ // GroupBy debug assert. Issue #26104.
+ Assert.StartsWith(
+ "Missing alias in the list",
+ (await Assert.ThrowsAsync(
+ () => base.Whats_new_2021_sample_6(async))).Message);
+
+ AssertSql();
+ }
+
+ public override async Task Whats_new_2021_sample_14(bool async)
+ {
+ await base.Whats_new_2021_sample_14(async);
+
+ AssertSql();
+ }
+
+ public override async Task Whats_new_2021_sample_15(bool async)
+ {
+ await base.Whats_new_2021_sample_15(async);
+
+ AssertSql(
+"""
+SELECT `t0`.`Id`, `t0`.`Age`, `t0`.`FirstName`, `t0`.`LastName`, `t0`.`MiddleInitial`
+FROM (
+ SELECT `f`.`Id`, `f`.`Size`
+ FROM `Person` AS `p`
+ LEFT JOIN `Feet` AS `f` ON `p`.`Id` = `f`.`Id`
+ GROUP BY `f`.`Id`, `f`.`Size`
+) AS `t`
+LEFT JOIN (
+ SELECT `t1`.`Id`, `t1`.`Age`, `t1`.`FirstName`, `t1`.`LastName`, `t1`.`MiddleInitial`, `t1`.`Id0`, `t1`.`Size`
+ FROM (
+ SELECT `p0`.`Id`, `p0`.`Age`, `p0`.`FirstName`, `p0`.`LastName`, `p0`.`MiddleInitial`, `f0`.`Id` AS `Id0`, `f0`.`Size`, ROW_NUMBER() OVER(PARTITION BY `f0`.`Id`, `f0`.`Size` ORDER BY `p0`.`Id` DESC) AS `row`
+ FROM `Person` AS `p0`
+ LEFT JOIN `Feet` AS `f0` ON `p0`.`Id` = `f0`.`Id`
+ ) AS `t1`
+ WHERE `t1`.`row` <= 1
+) AS `t0` ON ((`t`.`Id` = `t0`.`Id0`) OR (`t`.`Id` IS NULL AND (`t0`.`Id0` IS NULL))) AND ((`t`.`Size` = `t0`.`Size`) OR (`t`.`Size` IS NULL AND (`t0`.`Size` IS NULL)))
+""");
+ }
+
+ public override async Task Whats_new_2021_sample_16(bool async)
+ {
+ await base.Whats_new_2021_sample_16(async);
+
+ AssertSql();
+ }
+
+ public override async Task Min_Grouped_from_LINQ_101(bool async)
+ {
+ await base.Min_Grouped_from_LINQ_101(async);
+
+ AssertSql(
+"""
+SELECT `p`.`Category`, MIN(`p`.`UnitPrice`) AS `CheapestPrice`
+FROM `ProductForLinq` AS `p`
+GROUP BY `p`.`Category`
+""");
+ }
+
+ public override async Task Average_Grouped_from_LINQ_101(bool async)
+ {
+ await base.Average_Grouped_from_LINQ_101(async);
+
+ AssertSql(
+"""
+SELECT `p`.`Category`, AVG(`p`.`UnitPrice`) AS `AveragePrice`
+FROM `ProductForLinq` AS `p`
+GROUP BY `p`.`Category`
+""");
+ }
+
+ public override async Task Whats_new_2021_sample_8(bool async)
+ {
+ await base.Whats_new_2021_sample_8(async);
+
+ AssertSql(
+"""
+SELECT COUNT(*)
+FROM (
+ SELECT `f`.`Id`, `f`.`Size`
+ FROM `Person` AS `p`
+ LEFT JOIN `Feet` AS `f` ON `p`.`Id` = `f`.`Id`
+ GROUP BY `f`.`Id`, `f`.`Size`
+) AS `t`
+""");
+ }
+
+ public override async Task Whats_new_2021_sample_12(bool async)
+ {
+ await base.Whats_new_2021_sample_12(async);
+
+ AssertSql(
+"""
+SELECT `t`.`FirstName`, `t0`.`Id`, `t0`.`Age`, `t0`.`FirstName`, `t0`.`LastName`, `t0`.`MiddleInitial`, `t0`.`Id0`, `t0`.`Age0`, `t0`.`PersonId`, `t0`.`Style`
+FROM (
+ SELECT `p`.`FirstName`
+ FROM `Person` AS `p`
+ GROUP BY `p`.`FirstName`
+) AS `t`
+LEFT JOIN (
+ SELECT `p0`.`Id`, `p0`.`Age`, `p0`.`FirstName`, `p0`.`LastName`, `p0`.`MiddleInitial`, `s`.`Id` AS `Id0`, `s`.`Age` AS `Age0`, `s`.`PersonId`, `s`.`Style`
+ FROM `Person` AS `p0`
+ LEFT JOIN `Shoes` AS `s` ON `p0`.`Id` = `s`.`PersonId`
+) AS `t0` ON `t`.`FirstName` = `t0`.`FirstName`
+ORDER BY `t`.`FirstName`, `t0`.`Id`
+""");
+ }
+
+ public override async Task Whats_new_2021_sample_10(bool async)
+ {
+ await base.Whats_new_2021_sample_10(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`Age`, `t`.`Style`, `t0`.`Id`, `t0`.`Style`, `t0`.`Age`, `t0`.`Id0`
+FROM (
+ SELECT `p`.`Id`, `s`.`Age`, `s`.`Style`
+ FROM `Person` AS `p`
+ INNER JOIN `Shoes` AS `s` ON `p`.`Age` = `s`.`Age`
+ GROUP BY `p`.`Id`, `s`.`Style`, `s`.`Age`
+) AS `t`
+LEFT JOIN (
+ SELECT `s0`.`Id`, `s0`.`Style`, `s0`.`Age`, `p0`.`Id` AS `Id0`
+ FROM `Person` AS `p0`
+ INNER JOIN `Shoes` AS `s0` ON `p0`.`Age` = `s0`.`Age`
+) AS `t0` ON ((`t`.`Id` = `t0`.`Id0`) AND ((`t`.`Style` = `t0`.`Style`) OR (`t`.`Style` IS NULL AND (`t0`.`Style` IS NULL)))) AND (`t`.`Age` = `t0`.`Age`)
+ORDER BY `t`.`Id`, `t`.`Style`, `t`.`Age`, `t0`.`Id0`
+""");
+ }
+
+ public override async Task Whats_new_2021_sample_13(bool async)
+ {
+ await base.Whats_new_2021_sample_13(async);
+
+ AssertSql(
+"""
+SELECT `t`.`FirstName`, `t`.`MiddleInitial`, `p0`.`Id`, `p0`.`Age`, `p0`.`FirstName`, `p0`.`LastName`, `p0`.`MiddleInitial`
+FROM (
+ SELECT `p`.`FirstName`, `p`.`MiddleInitial`
+ FROM `Person` AS `p`
+ GROUP BY `p`.`FirstName`, `p`.`MiddleInitial`
+) AS `t`
+LEFT JOIN `Person` AS `p0` ON ((`t`.`FirstName` = `p0`.`FirstName`) OR (`t`.`FirstName` IS NULL AND (`p0`.`FirstName` IS NULL))) AND ((`t`.`MiddleInitial` = `p0`.`MiddleInitial`) OR (`t`.`MiddleInitial` IS NULL AND (`p0`.`MiddleInitial` IS NULL)))
+ORDER BY `t`.`FirstName`, `t`.`MiddleInitial`, `p0`.`Id`
+""");
+ }
+
+ public override async Task Cross_Join_with_Group_Join_from_LINQ_101(bool async)
+ {
+ await base.Cross_Join_with_Group_Join_from_LINQ_101(async);
+
+ AssertSql(
+"""
+SELECT `c`.`Id`, `c`.`CompanyName`, `c`.`Region`, `t`.`Id`
+FROM `CustomerForLinq` AS `c`
+INNER JOIN (
+ SELECT `o`.`Id`, `c0`.`Id` AS `Id0`
+ FROM `OrderForLinq` AS `o`
+ LEFT JOIN `CustomerForLinq` AS `c0` ON `o`.`CustomerId` = `c0`.`Id`
+) AS `t` ON `c`.`Id` = `t`.`Id0`
+""");
+ }
+
+ public override async Task Whats_new_2021_sample_2(bool async)
+ {
+ await base.Whats_new_2021_sample_2(async);
+
+ AssertSql(
+"""
+SELECT `t0`.`FirstName`, `t0`.`FullName`, `t0`.`c`
+FROM (
+ SELECT `p`.`FirstName`
+ FROM `Person` AS `p`
+ GROUP BY `p`.`FirstName`
+ ORDER BY `p`.`FirstName`
+ LIMIT 1
+) AS `t`
+LEFT JOIN (
+ SELECT `t1`.`FirstName`, `t1`.`FullName`, `t1`.`c`
+ FROM (
+ SELECT `p0`.`FirstName`, CONCAT(CONCAT(CONCAT(CONCAT(COALESCE(`p0`.`FirstName`, ''), ' '), COALESCE(`p0`.`MiddleInitial`, '')), ' '), COALESCE(`p0`.`LastName`, '')) AS `FullName`, 1 AS `c`, ROW_NUMBER() OVER(PARTITION BY `p0`.`FirstName` ORDER BY `p0`.`Id`) AS `row`
+ FROM `Person` AS `p0`
+ ) AS `t1`
+ WHERE `t1`.`row` <= 1
+) AS `t0` ON `t`.`FirstName` = `t0`.`FirstName`
+ORDER BY `t`.`FirstName`
+""");
+ }
+
+ public override async Task Whats_new_2021_sample_1(bool async)
+ {
+ await base.Whats_new_2021_sample_1(async);
+
+ AssertSql(
+"""
+SELECT `t0`.`Id`, `t0`.`Age`, `t0`.`FirstName`, `t0`.`LastName`, `t0`.`MiddleInitial`, `t`.`FirstName`, `s`.`Id`, `s`.`Age`, `s`.`PersonId`, `s`.`Style`
+FROM (
+ SELECT `p`.`FirstName`
+ FROM `Person` AS `p`
+ GROUP BY `p`.`FirstName`
+) AS `t`
+LEFT JOIN (
+ SELECT `t1`.`Id`, `t1`.`Age`, `t1`.`FirstName`, `t1`.`LastName`, `t1`.`MiddleInitial`
+ FROM (
+ SELECT `p0`.`Id`, `p0`.`Age`, `p0`.`FirstName`, `p0`.`LastName`, `p0`.`MiddleInitial`, ROW_NUMBER() OVER(PARTITION BY `p0`.`FirstName` ORDER BY `p0`.`FirstName`, `p0`.`LastName`) AS `row`
+ FROM `Person` AS `p0`
+ ) AS `t1`
+ WHERE `t1`.`row` <= 1
+) AS `t0` ON `t`.`FirstName` = `t0`.`FirstName`
+LEFT JOIN `Shoes` AS `s` ON `t0`.`Id` = `s`.`PersonId`
+ORDER BY `t`.`FirstName`, `t0`.`Id`
+""");
+ }
+
+ public override async Task Whats_new_2021_sample_7(bool async)
+ {
+ await base.Whats_new_2021_sample_7(async);
+
+ AssertSql(
+"""
+@__size_0='11'
+
+SELECT `p0`.`LastName`, `f`.`Size`, (
+ SELECT MIN(`f1`.`Size`)
+ FROM `Person` AS `p1`
+ LEFT JOIN `Feet` AS `f0` ON `p1`.`Id` = `f0`.`Id`
+ LEFT JOIN `Person` AS `p2` ON `f0`.`Id` = `p2`.`Id`
+ LEFT JOIN `Feet` AS `f1` ON `p1`.`Id` = `f1`.`Id`
+ WHERE (((`f0`.`Size` = @__size_0) AND `p1`.`MiddleInitial` IS NOT NULL) AND ((`f0`.`Id` <> 1) OR `f0`.`Id` IS NULL)) AND (((`f`.`Size` = `f0`.`Size`) OR (`f`.`Size` IS NULL AND (`f0`.`Size` IS NULL))) AND ((`p0`.`LastName` = `p2`.`LastName`) OR (`p0`.`LastName` IS NULL AND (`p2`.`LastName` IS NULL))))) AS `Min`
+FROM `Person` AS `p`
+LEFT JOIN `Feet` AS `f` ON `p`.`Id` = `f`.`Id`
+LEFT JOIN `Person` AS `p0` ON `f`.`Id` = `p0`.`Id`
+WHERE ((`f`.`Size` = @__size_0) AND `p`.`MiddleInitial` IS NOT NULL) AND ((`f`.`Id` <> 1) OR `f`.`Id` IS NULL)
+GROUP BY `f`.`Size`, `p0`.`LastName`
+""");
+ }
+
+ public override async Task Sum_Grouped_from_LINQ_101(bool async)
+ {
+ await base.Sum_Grouped_from_LINQ_101(async);
+
+ AssertSql(
+"""
+SELECT `p`.`Category`, COALESCE(SUM(`p`.`UnitsInStock`), 0) AS `TotalUnitsInStock`
+FROM `ProductForLinq` AS `p`
+GROUP BY `p`.`Category`
+""");
+ }
+
+ public override async Task Count_Grouped_from_LINQ_101(bool async)
+ {
+ await base.Count_Grouped_from_LINQ_101(async);
+
+ AssertSql(
+"""
+SELECT `p`.`Category`, COUNT(*) AS `ProductCount`
+FROM `ProductForLinq` AS `p`
+GROUP BY `p`.`Category`
+""");
+ }
+
+ public override async Task Whats_new_2021_sample_9(bool async)
+ {
+ await base.Whats_new_2021_sample_9(async);
+
+ AssertSql(
+"""
+SELECT `p`.`FirstName` AS `Feet`, (
+ SELECT COALESCE(SUM(`f`.`Size`), 0)
+ FROM `Person` AS `p0`
+ LEFT JOIN `Feet` AS `f` ON `p0`.`Id` = `f`.`Id`
+ WHERE (`p`.`FirstName` = `p0`.`FirstName`) OR (`p`.`FirstName` IS NULL AND (`p0`.`FirstName` IS NULL))) AS `Total`
+FROM `Person` AS `p`
+GROUP BY `p`.`FirstName`
+""");
+ }
+
+ public override async Task LongCount_Grouped_from_LINQ_101(bool async)
+ {
+ await base.LongCount_Grouped_from_LINQ_101(async);
+
+ AssertSql(
+"""
+SELECT `p`.`Category`, COUNT(*) AS `ProductLongCount`
+FROM `ProductForLinq` AS `p`
+GROUP BY `p`.`Category`
+""");
+ }
+
+ public override async Task Whats_new_2021_sample_4(bool async)
+ {
+ await base.Whats_new_2021_sample_4(async);
+
+ AssertSql(
+"""
+SELECT `s`.`Style` AS `Key`, (
+ SELECT `s0`.`Style`
+ FROM `Person` AS `p0`
+ INNER JOIN `Shoes` AS `s0` ON `p0`.`Age` = `s0`.`Age`
+ WHERE (`s`.`Style` = `s0`.`Style`) OR (`s`.`Style` IS NULL AND (`s0`.`Style` IS NULL))
+ LIMIT 1) AS `Style`, COUNT(*) AS `Count`
+FROM `Person` AS `p`
+INNER JOIN `Shoes` AS `s` ON `p`.`Age` = `s`.`Age`
+GROUP BY `s`.`Style`
+""");
+ }
+
+ public override async Task Left_Outer_Join_with_Group_Join_from_LINQ_101(bool async)
+ {
+ await base.Left_Outer_Join_with_Group_Join_from_LINQ_101(async);
+
+ AssertSql(
+"""
+SELECT `c`.`Id`, `c`.`CompanyName`, `c`.`Region`, `t`.`Id`, `t`.`Id0`, `o0`.`Id`, `o0`.`CustomerId`, `o0`.`OrderDate`, `o0`.`Total`, CASE
+ WHEN `t`.`Id` IS NULL THEN -1
+ ELSE `t`.`Id`
+END
+FROM `CustomerForLinq` AS `c`
+LEFT JOIN (
+ SELECT `o`.`Id`, `c0`.`Id` AS `Id0`
+ FROM `OrderForLinq` AS `o`
+ LEFT JOIN `CustomerForLinq` AS `c0` ON `o`.`CustomerId` = `c0`.`Id`
+) AS `t` ON `c`.`Id` = `t`.`Id0`
+LEFT JOIN `OrderForLinq` AS `o0` ON `c`.`Id` = `o0`.`CustomerId`
+ORDER BY `c`.`Id`, `t`.`Id`, `t`.`Id0`
+""");
+ }
+
+ public override async Task Max_Grouped_from_LINQ_101(bool async)
+ {
+ await base.Max_Grouped_from_LINQ_101(async);
+
+ AssertSql(
+"""
+SELECT `p`.`Category`, MAX(`p`.`UnitPrice`) AS `MostExpensivePrice`
+FROM `ProductForLinq` AS `p`
+GROUP BY `p`.`Category`
+""");
+ }
+
+ public override async Task Whats_new_2021_sample_11(bool async)
+ {
+ await base.Whats_new_2021_sample_11(async);
+
+ AssertSql(
+"""
+SELECT `t`.`LastName`, `t`.`c`, `t0`.`Id`, `t2`.`Id`, `t2`.`Age`, `t2`.`FirstName`, `t2`.`LastName`, `t2`.`MiddleInitial`, `t0`.`Age`, `t0`.`FirstName`, `t0`.`LastName`, `t0`.`MiddleInitial`
+FROM (
+ SELECT `p`.`LastName`, COUNT(*) AS `c`
+ FROM `Person` AS `p`
+ GROUP BY `p`.`LastName`
+) AS `t`
+LEFT JOIN (
+ SELECT `t1`.`Id`, `t1`.`Age`, `t1`.`FirstName`, `t1`.`LastName`, `t1`.`MiddleInitial`
+ FROM (
+ SELECT `p0`.`Id`, `p0`.`Age`, `p0`.`FirstName`, `p0`.`LastName`, `p0`.`MiddleInitial`, ROW_NUMBER() OVER(PARTITION BY `p0`.`LastName` ORDER BY `p0`.`Id`) AS `row`
+ FROM `Person` AS `p0`
+ ) AS `t1`
+ WHERE `t1`.`row` <= 1
+) AS `t0` ON `t`.`LastName` = `t0`.`LastName`
+LEFT JOIN (
+ SELECT `t3`.`Id`, `t3`.`Age`, `t3`.`FirstName`, `t3`.`LastName`, `t3`.`MiddleInitial`
+ FROM (
+ SELECT `p1`.`Id`, `p1`.`Age`, `p1`.`FirstName`, `p1`.`LastName`, `p1`.`MiddleInitial`, ROW_NUMBER() OVER(PARTITION BY `p1`.`LastName` ORDER BY `p1`.`Id`) AS `row`
+ FROM `Person` AS `p1`
+ ) AS `t3`
+ WHERE `t3`.`row` <= 2
+) AS `t2` ON `t`.`LastName` = `t2`.`LastName`
+ORDER BY `t`.`LastName` DESC, `t0`.`Id`, `t2`.`LastName`, `t2`.`Id`
+""");
+ }
+
+ private void AssertSql(params string[] expected)
+ => Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
+
+ public class Ef6GroupByMySqlFixture : Ef6GroupByFixtureBase
+ {
+ public TestSqlLoggerFactory TestSqlLoggerFactory
+ => (TestSqlLoggerFactory)ListLoggerFactory;
+
+ protected override ITestStoreFactory TestStoreFactory
+ => MySqlTestStoreFactory.Instance;
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
+ {
+ base.OnModelCreating(modelBuilder, context);
+
+ // modelBuilder.Entity().Property(o => o.OrderDate).HasColumnType("timestamp");
+ }
+ }
+}
diff --git a/test/EFCore.MySql.FunctionalTests/Query/EntitySplittingQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/EntitySplittingQueryMySqlTest.cs
new file mode 100644
index 000000000..16693507e
--- /dev/null
+++ b/test/EFCore.MySql.FunctionalTests/Query/EntitySplittingQueryMySqlTest.cs
@@ -0,0 +1,788 @@
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore.Query;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+using Pomelo.EntityFrameworkCore.MySql.FunctionalTests.TestUtilities;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests.Query;
+
+public class EntitySplittingQueryMySqlTest : EntitySplittingQueryTestBase
+{
+ public EntitySplittingQueryMySqlTest(ITestOutputHelper testOutputHelper)
+ {
+ // Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
+ }
+
+ [ConditionalFact]
+ public virtual void Check_all_tests_overridden()
+ => TestHelpers.AssertAllMethodsOverridden(GetType());
+
+ public override async Task Can_query_entity_which_is_split_in_two(bool async)
+ {
+ await base.Can_query_entity_which_is_split_in_two(async);
+
+ AssertSql(
+"""
+SELECT `e`.`Id`, `e`.`EntityThreeId`, `e`.`IntValue1`, `e`.`IntValue2`, `s`.`IntValue3`, `s`.`IntValue4`, `e`.`StringValue1`, `e`.`StringValue2`, `s`.`StringValue3`, `s`.`StringValue4`
+FROM `EntityOne` AS `e`
+INNER JOIN `SplitEntityOnePart` AS `s` ON `e`.`Id` = `s`.`Id`
+""");
+ }
+
+ public override async Task Can_query_entity_which_is_split_selecting_only_main_properties(bool async)
+ {
+ await base.Can_query_entity_which_is_split_selecting_only_main_properties(async);
+
+ AssertSql(
+"""
+SELECT `e`.`Id`, `e`.`IntValue1`, `e`.`StringValue1`
+FROM `EntityOne` AS `e`
+""");
+ }
+
+ public override async Task Can_query_entity_which_is_split_in_three(bool async)
+ {
+ await base.Can_query_entity_which_is_split_in_three(async);
+
+ AssertSql(
+"""
+SELECT `e`.`Id`, `e`.`EntityThreeId`, `e`.`IntValue1`, `e`.`IntValue2`, `s0`.`IntValue3`, `s`.`IntValue4`, `e`.`StringValue1`, `e`.`StringValue2`, `s0`.`StringValue3`, `s`.`StringValue4`
+FROM `EntityOne` AS `e`
+INNER JOIN `SplitEntityOnePart3` AS `s` ON `e`.`Id` = `s`.`Id`
+INNER JOIN `SplitEntityOnePart2` AS `s0` ON `e`.`Id` = `s0`.`Id`
+""");
+ }
+
+ public override async Task Can_query_entity_which_is_split_selecting_only_part_2_properties(bool async)
+ {
+ await base.Can_query_entity_which_is_split_selecting_only_part_2_properties(async);
+
+ AssertSql(
+"""
+SELECT `e`.`Id`, `s0`.`IntValue3`, `s0`.`StringValue3`
+FROM `EntityOne` AS `e`
+INNER JOIN `SplitEntityOnePart2` AS `s0` ON `e`.`Id` = `s0`.`Id`
+""");
+ }
+
+ public override async Task Can_query_entity_which_is_split_selecting_only_part_3_properties(bool async)
+ {
+ await base.Can_query_entity_which_is_split_selecting_only_part_3_properties(async);
+
+ AssertSql(
+"""
+SELECT `e`.`Id`, `s`.`IntValue4`, `s`.`StringValue4`
+FROM `EntityOne` AS `e`
+INNER JOIN `SplitEntityOnePart3` AS `s` ON `e`.`Id` = `s`.`Id`
+""");
+ }
+
+ public override async Task Include_reference_to_split_entity(bool async)
+ {
+ await base.Include_reference_to_split_entity(async);
+
+ AssertSql(
+"""
+SELECT `e`.`Id`, `e`.`EntityOneId`, `e`.`Name`, `t`.`Id`, `t`.`EntityThreeId`, `t`.`IntValue1`, `t`.`IntValue2`, `t`.`IntValue3`, `t`.`IntValue4`, `t`.`StringValue1`, `t`.`StringValue2`, `t`.`StringValue3`, `t`.`StringValue4`
+FROM `EntityTwo` AS `e`
+LEFT JOIN (
+ SELECT `e0`.`Id`, `e0`.`EntityThreeId`, `e0`.`IntValue1`, `e0`.`IntValue2`, `s0`.`IntValue3`, `s`.`IntValue4`, `e0`.`StringValue1`, `e0`.`StringValue2`, `s0`.`StringValue3`, `s`.`StringValue4`
+ FROM `EntityOne` AS `e0`
+ INNER JOIN `SplitEntityOnePart3` AS `s` ON `e0`.`Id` = `s`.`Id`
+ INNER JOIN `SplitEntityOnePart2` AS `s0` ON `e0`.`Id` = `s0`.`Id`
+) AS `t` ON `e`.`EntityOneId` = `t`.`Id`
+""");
+ }
+
+ public override async Task Include_collection_to_split_entity(bool async)
+ {
+ await base.Include_collection_to_split_entity(async);
+
+ AssertSql(
+"""
+SELECT `e`.`Id`, `e`.`Name`, `t`.`Id`, `t`.`EntityThreeId`, `t`.`IntValue1`, `t`.`IntValue2`, `t`.`IntValue3`, `t`.`IntValue4`, `t`.`StringValue1`, `t`.`StringValue2`, `t`.`StringValue3`, `t`.`StringValue4`
+FROM `EntityThree` AS `e`
+LEFT JOIN (
+ SELECT `e0`.`Id`, `e0`.`EntityThreeId`, `e0`.`IntValue1`, `e0`.`IntValue2`, `s0`.`IntValue3`, `s`.`IntValue4`, `e0`.`StringValue1`, `e0`.`StringValue2`, `s0`.`StringValue3`, `s`.`StringValue4`
+ FROM `EntityOne` AS `e0`
+ INNER JOIN `SplitEntityOnePart3` AS `s` ON `e0`.`Id` = `s`.`Id`
+ INNER JOIN `SplitEntityOnePart2` AS `s0` ON `e0`.`Id` = `s0`.`Id`
+) AS `t` ON `e`.`Id` = `t`.`EntityThreeId`
+ORDER BY `e`.`Id`
+""");
+ }
+
+ public override async Task Include_reference_to_split_entity_including_reference(bool async)
+ {
+ await base.Include_reference_to_split_entity_including_reference(async);
+
+ AssertSql(
+"""
+SELECT `e`.`Id`, `e`.`EntityOneId`, `e`.`Name`, `t`.`Id`, `t`.`EntityThreeId`, `t`.`IntValue1`, `t`.`IntValue2`, `t`.`IntValue3`, `t`.`IntValue4`, `t`.`StringValue1`, `t`.`StringValue2`, `t`.`StringValue3`, `t`.`StringValue4`, `e1`.`Id`, `e1`.`Name`
+FROM `EntityTwo` AS `e`
+LEFT JOIN (
+ SELECT `e0`.`Id`, `e0`.`EntityThreeId`, `e0`.`IntValue1`, `e0`.`IntValue2`, `s0`.`IntValue3`, `s`.`IntValue4`, `e0`.`StringValue1`, `e0`.`StringValue2`, `s0`.`StringValue3`, `s`.`StringValue4`
+ FROM `EntityOne` AS `e0`
+ INNER JOIN `SplitEntityOnePart3` AS `s` ON `e0`.`Id` = `s`.`Id`
+ INNER JOIN `SplitEntityOnePart2` AS `s0` ON `e0`.`Id` = `s0`.`Id`
+) AS `t` ON `e`.`EntityOneId` = `t`.`Id`
+LEFT JOIN `EntityThree` AS `e1` ON `t`.`EntityThreeId` = `e1`.`Id`
+""");
+ }
+
+ public override async Task Include_collection_to_split_entity_including_collection(bool async)
+ {
+ await base.Include_collection_to_split_entity_including_collection(async);
+
+ AssertSql(
+"""
+SELECT `e`.`Id`, `e`.`Name`, `t`.`Id`, `t`.`EntityThreeId`, `t`.`IntValue1`, `t`.`IntValue2`, `t`.`IntValue3`, `t`.`IntValue4`, `t`.`StringValue1`, `t`.`StringValue2`, `t`.`StringValue3`, `t`.`StringValue4`, `t`.`Id0`, `t`.`EntityOneId`, `t`.`Name`
+FROM `EntityThree` AS `e`
+LEFT JOIN (
+ SELECT `e0`.`Id`, `e0`.`EntityThreeId`, `e0`.`IntValue1`, `e0`.`IntValue2`, `s0`.`IntValue3`, `s`.`IntValue4`, `e0`.`StringValue1`, `e0`.`StringValue2`, `s0`.`StringValue3`, `s`.`StringValue4`, `e1`.`Id` AS `Id0`, `e1`.`EntityOneId`, `e1`.`Name`
+ FROM `EntityOne` AS `e0`
+ INNER JOIN `SplitEntityOnePart3` AS `s` ON `e0`.`Id` = `s`.`Id`
+ INNER JOIN `SplitEntityOnePart2` AS `s0` ON `e0`.`Id` = `s0`.`Id`
+ LEFT JOIN `EntityTwo` AS `e1` ON `e0`.`Id` = `e1`.`EntityOneId`
+) AS `t` ON `e`.`Id` = `t`.`EntityThreeId`
+ORDER BY `e`.`Id`, `t`.`Id`
+""");
+ }
+
+ public override async Task Include_reference_on_split_entity(bool async)
+ {
+ await base.Include_reference_on_split_entity(async);
+
+ AssertSql(
+"""
+SELECT `e`.`Id`, `e`.`EntityThreeId`, `e`.`IntValue1`, `e`.`IntValue2`, `s0`.`IntValue3`, `s`.`IntValue4`, `e`.`StringValue1`, `e`.`StringValue2`, `s0`.`StringValue3`, `s`.`StringValue4`, `e0`.`Id`, `e0`.`Name`
+FROM `EntityOne` AS `e`
+INNER JOIN `SplitEntityOnePart3` AS `s` ON `e`.`Id` = `s`.`Id`
+INNER JOIN `SplitEntityOnePart2` AS `s0` ON `e`.`Id` = `s0`.`Id`
+LEFT JOIN `EntityThree` AS `e0` ON `e`.`EntityThreeId` = `e0`.`Id`
+""");
+ }
+
+ public override async Task Include_collection_on_split_entity(bool async)
+ {
+ await base.Include_collection_on_split_entity(async);
+
+ AssertSql(
+"""
+SELECT `e`.`Id`, `e`.`EntityThreeId`, `e`.`IntValue1`, `e`.`IntValue2`, `s0`.`IntValue3`, `s`.`IntValue4`, `e`.`StringValue1`, `e`.`StringValue2`, `s0`.`StringValue3`, `s`.`StringValue4`, `e0`.`Id`, `e0`.`EntityOneId`, `e0`.`Name`
+FROM `EntityOne` AS `e`
+INNER JOIN `SplitEntityOnePart3` AS `s` ON `e`.`Id` = `s`.`Id`
+INNER JOIN `SplitEntityOnePart2` AS `s0` ON `e`.`Id` = `s0`.`Id`
+LEFT JOIN `EntityTwo` AS `e0` ON `e`.`Id` = `e0`.`EntityOneId`
+ORDER BY `e`.`Id`
+""");
+ }
+
+ public override async Task Custom_projection_trim_when_multiple_tables(bool async)
+ {
+ await base.Custom_projection_trim_when_multiple_tables(async);
+
+ AssertSql(
+"""
+SELECT `e`.`IntValue1`, `s0`.`IntValue3`, `e0`.`Id`, `e0`.`Name`
+FROM `EntityOne` AS `e`
+INNER JOIN `SplitEntityOnePart2` AS `s0` ON `e`.`Id` = `s0`.`Id`
+LEFT JOIN `EntityThree` AS `e0` ON `e`.`EntityThreeId` = `e0`.`Id`
+""");
+ }
+
+ public override async Task Normal_entity_owning_a_split_reference_with_main_fragment_sharing(bool async)
+ {
+ await base.Normal_entity_owning_a_split_reference_with_main_fragment_sharing(async);
+
+ AssertSql(
+"""
+SELECT `e`.`Id`, `e`.`EntityThreeId`, `e`.`IntValue1`, `e`.`IntValue2`, `e`.`IntValue3`, `e`.`IntValue4`, `e`.`StringValue1`, `e`.`StringValue2`, `e`.`StringValue3`, `e`.`StringValue4`, `e`.`OwnedReference_Id`, `e`.`OwnedReference_OwnedIntValue1`, `e`.`OwnedReference_OwnedIntValue2`, `o0`.`OwnedIntValue3`, `o`.`OwnedIntValue4`, `e`.`OwnedReference_OwnedStringValue1`, `e`.`OwnedReference_OwnedStringValue2`, `o0`.`OwnedStringValue3`, `o`.`OwnedStringValue4`
+FROM `EntityOne` AS `e`
+LEFT JOIN `OwnedReferenceExtras2` AS `o` ON `e`.`Id` = `o`.`EntityOneId`
+LEFT JOIN `OwnedReferenceExtras1` AS `o0` ON `e`.`Id` = `o0`.`EntityOneId`
+""");
+ }
+
+ public override async Task Normal_entity_owning_a_split_reference_with_main_fragment_sharing_custom_projection(bool async)
+ {
+ await base.Normal_entity_owning_a_split_reference_with_main_fragment_sharing_custom_projection(async);
+
+ AssertSql(
+"""
+SELECT `e`.`Id`, CASE
+ WHEN (((`e`.`OwnedReference_Id` IS NOT NULL AND (`e`.`OwnedReference_OwnedIntValue1` IS NOT NULL)) AND `e`.`OwnedReference_OwnedIntValue2` IS NOT NULL) AND `o0`.`OwnedIntValue3` IS NOT NULL) AND `o`.`OwnedIntValue4` IS NOT NULL THEN `o`.`OwnedIntValue4`
+END AS `OwnedIntValue4`, CASE
+ WHEN (((`e`.`OwnedReference_Id` IS NOT NULL AND (`e`.`OwnedReference_OwnedIntValue1` IS NOT NULL)) AND `e`.`OwnedReference_OwnedIntValue2` IS NOT NULL) AND `o0`.`OwnedIntValue3` IS NOT NULL) AND `o`.`OwnedIntValue4` IS NOT NULL THEN `o`.`OwnedStringValue4`
+END AS `OwnedStringValue4`
+FROM `EntityOnes` AS `e`
+LEFT JOIN `OwnedReferenceExtras2` AS `o` ON `e`.`Id` = `o`.`EntityOneId`
+LEFT JOIN `OwnedReferenceExtras1` AS `o0` ON `e`.`Id` = `o0`.`EntityOneId`
+""");
+ }
+
+ [ConditionalTheory(Skip = "Issue29075")]
+ public override async Task Normal_entity_owning_a_split_reference_with_main_fragment_not_sharing(bool async)
+ {
+ await base.Normal_entity_owning_a_split_reference_with_main_fragment_not_sharing(async);
+
+ AssertSql();
+ }
+
+ [ConditionalTheory(Skip = "Issue29075")]
+ public override async Task Normal_entity_owning_a_split_reference_with_main_fragment_not_sharing_custom_projection(bool async)
+ {
+ await base.Normal_entity_owning_a_split_reference_with_main_fragment_not_sharing_custom_projection(async);
+
+ AssertSql();
+ }
+
+ [ConditionalTheory(Skip = "Issue29075")]
+ public override async Task Normal_entity_owning_a_split_collection(bool async)
+ {
+ await base.Normal_entity_owning_a_split_collection(async);
+
+ AssertSql();
+ }
+
+ public override async Task Normal_entity_owning_a_split_reference_with_main_fragment_sharing_multiple_level(bool async)
+ {
+ await base.Normal_entity_owning_a_split_reference_with_main_fragment_sharing_multiple_level(async);
+
+ AssertSql(
+"""
+SELECT `e`.`Id`, `e`.`EntityThreeId`, `e`.`IntValue1`, `e`.`IntValue2`, `e`.`IntValue3`, `e`.`IntValue4`, `e`.`StringValue1`, `e`.`StringValue2`, `e`.`StringValue3`, `e`.`StringValue4`, `e`.`OwnedReference_Id`, `e`.`OwnedReference_OwnedIntValue1`, `e`.`OwnedReference_OwnedIntValue2`, `o0`.`OwnedIntValue3`, `o`.`OwnedIntValue4`, `e`.`OwnedReference_OwnedStringValue1`, `e`.`OwnedReference_OwnedStringValue2`, `o0`.`OwnedStringValue3`, `o`.`OwnedStringValue4`, `e`.`OwnedReference_OwnedNestedReference_Id`, `e`.`OwnedReference_OwnedNestedReference_OwnedNestedIntValue1`, `e`.`OwnedReference_OwnedNestedReference_OwnedNestedIntValue2`, `o2`.`OwnedNestedIntValue3`, `o1`.`OwnedNestedIntValue4`, `e`.`OwnedReference_OwnedNestedReference_OwnedNestedStringValue1`, `e`.`OwnedReference_OwnedNestedReference_OwnedNestedStringValue2`, `o2`.`OwnedNestedStringValue3`, `o1`.`OwnedNestedStringValue4`
+FROM `EntityOnes` AS `e`
+LEFT JOIN `OwnedReferenceExtras2` AS `o` ON `e`.`Id` = `o`.`EntityOneId`
+LEFT JOIN `OwnedReferenceExtras1` AS `o0` ON `e`.`Id` = `o0`.`EntityOneId`
+LEFT JOIN `OwnedNestedReferenceExtras2` AS `o1` ON `e`.`Id` = `o1`.`OwnedReferenceEntityOneId`
+LEFT JOIN `OwnedNestedReferenceExtras1` AS `o2` ON `e`.`Id` = `o2`.`OwnedReferenceEntityOneId`
+""");
+ }
+
+ public override async Task Split_entity_owning_a_reference(bool async)
+ {
+ await base.Split_entity_owning_a_reference(async);
+
+ AssertSql(
+"""
+SELECT `e`.`Id`, `e`.`EntityThreeId`, `e`.`IntValue1`, `e`.`IntValue2`, `s0`.`IntValue3`, `s`.`IntValue4`, `e`.`StringValue1`, `e`.`StringValue2`, `s0`.`StringValue3`, `s`.`StringValue4`, `e`.`OwnedReference_Id`, `e`.`OwnedReference_OwnedIntValue1`, `e`.`OwnedReference_OwnedIntValue2`, `e`.`OwnedReference_OwnedIntValue3`, `e`.`OwnedReference_OwnedIntValue4`, `e`.`OwnedReference_OwnedStringValue1`, `e`.`OwnedReference_OwnedStringValue2`, `e`.`OwnedReference_OwnedStringValue3`, `e`.`OwnedReference_OwnedStringValue4`
+FROM `EntityOne` AS `e`
+INNER JOIN `SplitEntityOnePart3` AS `s` ON `e`.`Id` = `s`.`Id`
+INNER JOIN `SplitEntityOnePart2` AS `s0` ON `e`.`Id` = `s0`.`Id`
+""");
+ }
+
+ public override async Task Split_entity_owning_a_collection(bool async)
+ {
+ await base.Split_entity_owning_a_collection(async);
+
+ AssertSql(
+"""
+SELECT `e`.`Id`, `e`.`EntityThreeId`, `e`.`IntValue1`, `e`.`IntValue2`, `s0`.`IntValue3`, `s`.`IntValue4`, `e`.`StringValue1`, `e`.`StringValue2`, `s0`.`StringValue3`, `s`.`StringValue4`, `o`.`EntityOneId`, `o`.`Id`, `o`.`OwnedIntValue1`, `o`.`OwnedIntValue2`, `o`.`OwnedIntValue3`, `o`.`OwnedIntValue4`, `o`.`OwnedStringValue1`, `o`.`OwnedStringValue2`, `o`.`OwnedStringValue3`, `o`.`OwnedStringValue4`
+FROM `EntityOne` AS `e`
+INNER JOIN `SplitEntityOnePart3` AS `s` ON `e`.`Id` = `s`.`Id`
+INNER JOIN `SplitEntityOnePart2` AS `s0` ON `e`.`Id` = `s0`.`Id`
+LEFT JOIN `OwnedCollection` AS `o` ON `e`.`Id` = `o`.`EntityOneId`
+ORDER BY `e`.`Id`, `o`.`EntityOneId`
+""");
+ }
+
+ [ConditionalTheory(Skip = "Issue29075")]
+ public override async Task Split_entity_owning_a_split_reference_without_table_sharing(bool async)
+ {
+ await base.Split_entity_owning_a_split_reference_without_table_sharing(async);
+
+ AssertSql();
+ }
+
+ [ConditionalTheory(Skip = "Issue29075")]
+ public override async Task Split_entity_owning_a_split_collection(bool async)
+ {
+ await base.Split_entity_owning_a_split_collection(async);
+
+ AssertSql();
+ }
+
+ public override async Task Split_entity_owning_a_split_reference_with_table_sharing_1(bool async)
+ {
+ await base.Split_entity_owning_a_split_reference_with_table_sharing_1(async);
+
+ AssertSql(
+"""
+SELECT `s`.`Id`, `s`.`EntityThreeId`, `s`.`IntValue1`, `s`.`IntValue2`, `s1`.`IntValue3`, `s0`.`IntValue4`, `s`.`StringValue1`, `s`.`StringValue2`, `s1`.`StringValue3`, `s0`.`StringValue4`, `s`.`OwnedReference_Id`, `s`.`OwnedReference_OwnedIntValue1`, `s`.`OwnedReference_OwnedIntValue2`, `s1`.`OwnedReference_OwnedIntValue3`, `s0`.`OwnedReference_OwnedIntValue4`, `s`.`OwnedReference_OwnedStringValue1`, `s`.`OwnedReference_OwnedStringValue2`, `s1`.`OwnedReference_OwnedStringValue3`, `s0`.`OwnedReference_OwnedStringValue4`
+FROM `SplitEntityOnePart1` AS `s`
+INNER JOIN `SplitEntityOnePart3` AS `s0` ON `s`.`Id` = `s0`.`Id`
+INNER JOIN `SplitEntityOnePart2` AS `s1` ON `s`.`Id` = `s1`.`Id`
+""");
+ }
+
+ public override async Task Split_entity_owning_a_split_reference_with_table_sharing_4(bool async)
+ {
+ await base.Split_entity_owning_a_split_reference_with_table_sharing_4(async);
+
+ AssertSql(
+"""
+SELECT `s`.`Id`, `s`.`EntityThreeId`, `s`.`IntValue1`, `s`.`IntValue2`, `s1`.`IntValue3`, `s0`.`IntValue4`, `s`.`StringValue1`, `s`.`StringValue2`, `s1`.`StringValue3`, `s0`.`StringValue4`, `s`.`OwnedReference_Id`, `s`.`OwnedReference_OwnedIntValue1`, `s`.`OwnedReference_OwnedIntValue2`, `s1`.`OwnedReference_OwnedIntValue3`, `o`.`OwnedIntValue4`, `s`.`OwnedReference_OwnedStringValue1`, `s`.`OwnedReference_OwnedStringValue2`, `s1`.`OwnedReference_OwnedStringValue3`, `o`.`OwnedStringValue4`
+FROM `SplitEntityOnePart1` AS `s`
+INNER JOIN `SplitEntityOnePart3` AS `s0` ON `s`.`Id` = `s0`.`Id`
+INNER JOIN `SplitEntityOnePart2` AS `s1` ON `s`.`Id` = `s1`.`Id`
+LEFT JOIN `OwnedReferencePart3` AS `o` ON `s`.`Id` = `o`.`EntityOneId`
+""");
+ }
+
+ [ConditionalTheory(Skip = "Issue29075")]
+ public override async Task Split_entity_owning_a_split_reference_with_table_sharing_6(bool async)
+ {
+ await base.Split_entity_owning_a_split_reference_with_table_sharing_6(async);
+
+ AssertSql(
+"""
+SELECT s."Id", s."EntityThreeId", s."IntValue1", s."IntValue2", s1."IntValue3", s0."IntValue4", s."StringValue1", s."StringValue2", s1."StringValue3", s0."StringValue4", s1."Id", s1."OwnedReference_Id", s1."OwnedReference_OwnedIntValue1", s1."OwnedReference_OwnedIntValue2", o0."OwnedIntValue3", o."OwnedIntValue4", s1."OwnedReference_OwnedStringValue1", s1."OwnedReference_OwnedStringValue2", o0."OwnedStringValue3", o."OwnedStringValue4"
+FROM "SplitEntityOnePart1" AS s
+INNER JOIN "SplitEntityOnePart3" AS s0 ON s."Id" = s0."Id"
+INNER JOIN "SplitEntityOnePart2" AS s1 ON s."Id" = s1."Id"
+LEFT JOIN "OwnedReferencePart3" AS o ON s1."Id" = o."EntityOneId"
+LEFT JOIN "OwnedReferencePart2" AS o0 ON s1."Id" = o0."EntityOneId"
+""");
+ }
+
+ public override async Task Tph_entity_owning_a_split_reference_on_base_with_table_sharing(bool async)
+ {
+ await base.Tph_entity_owning_a_split_reference_on_base_with_table_sharing(async);
+
+ AssertSql(
+"""
+SELECT `b`.`Id`, `b`.`BaseValue`, `b`.`Discriminator`, `b`.`MiddleValue`, `b`.`SiblingValue`, `b`.`LeafValue`, `b`.`OwnedReference_Id`, `b`.`OwnedReference_OwnedIntValue1`, `b`.`OwnedReference_OwnedIntValue2`, `o0`.`OwnedIntValue3`, `o`.`OwnedIntValue4`, `b`.`OwnedReference_OwnedStringValue1`, `b`.`OwnedReference_OwnedStringValue2`, `o0`.`OwnedStringValue3`, `o`.`OwnedStringValue4`
+FROM `BaseEntity` AS `b`
+LEFT JOIN `OwnedReferencePart4` AS `o` ON `b`.`Id` = `o`.`BaseEntityId`
+LEFT JOIN `OwnedReferencePart3` AS `o0` ON `b`.`Id` = `o0`.`BaseEntityId`
+""");
+ }
+
+ public override async Task Tpt_entity_owning_a_split_reference_on_base_with_table_sharing(bool async)
+ {
+ await base.Tpt_entity_owning_a_split_reference_on_base_with_table_sharing(async);
+
+ AssertSql(
+"""
+SELECT `b`.`Id`, `b`.`BaseValue`, `m`.`MiddleValue`, `s`.`SiblingValue`, `l`.`LeafValue`, CASE
+ WHEN `l`.`Id` IS NOT NULL THEN 'LeafEntity'
+ WHEN `s`.`Id` IS NOT NULL THEN 'SiblingEntity'
+ WHEN `m`.`Id` IS NOT NULL THEN 'MiddleEntity'
+END AS `Discriminator`, `b`.`OwnedReference_Id`, `b`.`OwnedReference_OwnedIntValue1`, `b`.`OwnedReference_OwnedIntValue2`, `o0`.`OwnedIntValue3`, `o`.`OwnedIntValue4`, `b`.`OwnedReference_OwnedStringValue1`, `b`.`OwnedReference_OwnedStringValue2`, `o0`.`OwnedStringValue3`, `o`.`OwnedStringValue4`
+FROM `BaseEntity` AS `b`
+LEFT JOIN `MiddleEntity` AS `m` ON `b`.`Id` = `m`.`Id`
+LEFT JOIN `SiblingEntity` AS `s` ON `b`.`Id` = `s`.`Id`
+LEFT JOIN `LeafEntity` AS `l` ON `b`.`Id` = `l`.`Id`
+LEFT JOIN `OwnedReferencePart4` AS `o` ON `b`.`Id` = `o`.`BaseEntityId`
+LEFT JOIN `OwnedReferencePart3` AS `o0` ON `b`.`Id` = `o0`.`BaseEntityId`
+""");
+ }
+
+ public override async Task Tph_entity_owning_a_split_reference_on_middle_with_table_sharing(bool async)
+ {
+ await base.Tph_entity_owning_a_split_reference_on_middle_with_table_sharing(async);
+
+ AssertSql(
+"""
+SELECT `b`.`Id`, `b`.`BaseValue`, `b`.`Discriminator`, `b`.`MiddleValue`, `b`.`SiblingValue`, `b`.`LeafValue`, `b`.`OwnedReference_Id`, `b`.`OwnedReference_OwnedIntValue1`, `b`.`OwnedReference_OwnedIntValue2`, `o0`.`OwnedIntValue3`, `o`.`OwnedIntValue4`, `b`.`OwnedReference_OwnedStringValue1`, `b`.`OwnedReference_OwnedStringValue2`, `o0`.`OwnedStringValue3`, `o`.`OwnedStringValue4`
+FROM `BaseEntity` AS `b`
+LEFT JOIN `OwnedReferencePart4` AS `o` ON `b`.`Id` = `o`.`MiddleEntityId`
+LEFT JOIN `OwnedReferencePart3` AS `o0` ON `b`.`Id` = `o0`.`MiddleEntityId`
+""");
+ }
+
+ public override async Task Tpt_entity_owning_a_split_reference_on_middle_with_table_sharing(bool async)
+ {
+ await base.Tpt_entity_owning_a_split_reference_on_middle_with_table_sharing(async);
+
+ AssertSql(
+"""
+SELECT `b`.`Id`, `b`.`BaseValue`, `m`.`MiddleValue`, `s`.`SiblingValue`, `l`.`LeafValue`, CASE
+ WHEN `l`.`Id` IS NOT NULL THEN 'LeafEntity'
+ WHEN `s`.`Id` IS NOT NULL THEN 'SiblingEntity'
+ WHEN `m`.`Id` IS NOT NULL THEN 'MiddleEntity'
+END AS `Discriminator`, `m`.`Id`, `m`.`OwnedReference_Id`, `m`.`OwnedReference_OwnedIntValue1`, `m`.`OwnedReference_OwnedIntValue2`, `o0`.`OwnedIntValue3`, `o`.`OwnedIntValue4`, `m`.`OwnedReference_OwnedStringValue1`, `m`.`OwnedReference_OwnedStringValue2`, `o0`.`OwnedStringValue3`, `o`.`OwnedStringValue4`
+FROM `BaseEntity` AS `b`
+LEFT JOIN `MiddleEntity` AS `m` ON `b`.`Id` = `m`.`Id`
+LEFT JOIN `SiblingEntity` AS `s` ON `b`.`Id` = `s`.`Id`
+LEFT JOIN `LeafEntity` AS `l` ON `b`.`Id` = `l`.`Id`
+LEFT JOIN `OwnedReferencePart4` AS `o` ON `m`.`Id` = `o`.`MiddleEntityId`
+LEFT JOIN `OwnedReferencePart3` AS `o0` ON `m`.`Id` = `o0`.`MiddleEntityId`
+""");
+ }
+
+ public override async Task Tph_entity_owning_a_split_reference_on_leaf_with_table_sharing(bool async)
+ {
+ await base.Tph_entity_owning_a_split_reference_on_leaf_with_table_sharing(async);
+
+ AssertSql(
+"""
+SELECT `b`.`Id`, `b`.`BaseValue`, `b`.`Discriminator`, `b`.`MiddleValue`, `b`.`SiblingValue`, `b`.`LeafValue`, `b`.`OwnedReference_Id`, `b`.`OwnedReference_OwnedIntValue1`, `b`.`OwnedReference_OwnedIntValue2`, `o0`.`OwnedIntValue3`, `o`.`OwnedIntValue4`, `b`.`OwnedReference_OwnedStringValue1`, `b`.`OwnedReference_OwnedStringValue2`, `o0`.`OwnedStringValue3`, `o`.`OwnedStringValue4`
+FROM `BaseEntity` AS `b`
+LEFT JOIN `OwnedReferencePart4` AS `o` ON `b`.`Id` = `o`.`LeafEntityId`
+LEFT JOIN `OwnedReferencePart3` AS `o0` ON `b`.`Id` = `o0`.`LeafEntityId`
+""");
+ }
+
+ public override async Task Tpt_entity_owning_a_split_reference_on_leaf_with_table_sharing(bool async)
+ {
+ await base.Tpt_entity_owning_a_split_reference_on_leaf_with_table_sharing(async);
+
+ AssertSql(
+"""
+SELECT `b`.`Id`, `b`.`BaseValue`, `m`.`MiddleValue`, `s`.`SiblingValue`, `l`.`LeafValue`, CASE
+ WHEN `l`.`Id` IS NOT NULL THEN 'LeafEntity'
+ WHEN `s`.`Id` IS NOT NULL THEN 'SiblingEntity'
+ WHEN `m`.`Id` IS NOT NULL THEN 'MiddleEntity'
+END AS `Discriminator`, `l`.`Id`, `l`.`OwnedReference_Id`, `l`.`OwnedReference_OwnedIntValue1`, `l`.`OwnedReference_OwnedIntValue2`, `o0`.`OwnedIntValue3`, `o`.`OwnedIntValue4`, `l`.`OwnedReference_OwnedStringValue1`, `l`.`OwnedReference_OwnedStringValue2`, `o0`.`OwnedStringValue3`, `o`.`OwnedStringValue4`
+FROM `BaseEntity` AS `b`
+LEFT JOIN `MiddleEntity` AS `m` ON `b`.`Id` = `m`.`Id`
+LEFT JOIN `SiblingEntity` AS `s` ON `b`.`Id` = `s`.`Id`
+LEFT JOIN `LeafEntity` AS `l` ON `b`.`Id` = `l`.`Id`
+LEFT JOIN `OwnedReferencePart4` AS `o` ON `l`.`Id` = `o`.`LeafEntityId`
+LEFT JOIN `OwnedReferencePart3` AS `o0` ON `l`.`Id` = `o0`.`LeafEntityId`
+""");
+ }
+
+ public override async Task Tpc_entity_owning_a_split_reference_on_leaf_with_table_sharing(bool async)
+ {
+ await base.Tpc_entity_owning_a_split_reference_on_leaf_with_table_sharing(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`BaseValue`, `t`.`MiddleValue`, `t`.`SiblingValue`, `t`.`LeafValue`, `t`.`Discriminator`, `l`.`Id`, `l`.`OwnedReference_Id`, `l`.`OwnedReference_OwnedIntValue1`, `l`.`OwnedReference_OwnedIntValue2`, `o0`.`OwnedIntValue3`, `o`.`OwnedIntValue4`, `l`.`OwnedReference_OwnedStringValue1`, `l`.`OwnedReference_OwnedStringValue2`, `o0`.`OwnedStringValue3`, `o`.`OwnedStringValue4`
+FROM (
+ SELECT `b`.`Id`, `b`.`BaseValue`, NULL AS `MiddleValue`, NULL AS `SiblingValue`, NULL AS `LeafValue`, 'BaseEntity' AS `Discriminator`
+ FROM `BaseEntity` AS `b`
+ UNION ALL
+ SELECT `m`.`Id`, `m`.`BaseValue`, `m`.`MiddleValue`, NULL AS `SiblingValue`, NULL AS `LeafValue`, 'MiddleEntity' AS `Discriminator`
+ FROM `MiddleEntity` AS `m`
+ UNION ALL
+ SELECT `s`.`Id`, `s`.`BaseValue`, NULL AS `MiddleValue`, `s`.`SiblingValue`, NULL AS `LeafValue`, 'SiblingEntity' AS `Discriminator`
+ FROM `SiblingEntity` AS `s`
+ UNION ALL
+ SELECT `l0`.`Id`, `l0`.`BaseValue`, `l0`.`MiddleValue`, NULL AS `SiblingValue`, `l0`.`LeafValue`, 'LeafEntity' AS `Discriminator`
+ FROM `LeafEntity` AS `l0`
+) AS `t`
+LEFT JOIN `LeafEntity` AS `l` ON `t`.`Id` = `l`.`Id`
+LEFT JOIN `OwnedReferencePart4` AS `o` ON `l`.`Id` = `o`.`LeafEntityId`
+LEFT JOIN `OwnedReferencePart3` AS `o0` ON `l`.`Id` = `o0`.`LeafEntityId`
+""");
+ }
+
+ public override async Task Tph_entity_owning_a_split_reference_on_base_with_table_sharing_querying_sibling(bool async)
+ {
+ await base.Tph_entity_owning_a_split_reference_on_base_with_table_sharing_querying_sibling(async);
+
+ AssertSql(
+"""
+SELECT `b`.`Id`, `b`.`BaseValue`, `b`.`Discriminator`, `b`.`SiblingValue`, `b`.`OwnedReference_Id`, `b`.`OwnedReference_OwnedIntValue1`, `b`.`OwnedReference_OwnedIntValue2`, `o0`.`OwnedIntValue3`, `o`.`OwnedIntValue4`, `b`.`OwnedReference_OwnedStringValue1`, `b`.`OwnedReference_OwnedStringValue2`, `o0`.`OwnedStringValue3`, `o`.`OwnedStringValue4`
+FROM `BaseEntity` AS `b`
+LEFT JOIN `OwnedReferencePart4` AS `o` ON `b`.`Id` = `o`.`BaseEntityId`
+LEFT JOIN `OwnedReferencePart3` AS `o0` ON `b`.`Id` = `o0`.`BaseEntityId`
+WHERE `b`.`Discriminator` = 'SiblingEntity'
+""");
+ }
+
+ public override async Task Tpt_entity_owning_a_split_reference_on_base_with_table_sharing_querying_sibling(bool async)
+ {
+ await base.Tpt_entity_owning_a_split_reference_on_base_with_table_sharing_querying_sibling(async);
+
+ AssertSql(
+"""
+SELECT `b`.`Id`, `b`.`BaseValue`, `s`.`SiblingValue`, `b`.`OwnedReference_Id`, `b`.`OwnedReference_OwnedIntValue1`, `b`.`OwnedReference_OwnedIntValue2`, `o0`.`OwnedIntValue3`, `o`.`OwnedIntValue4`, `b`.`OwnedReference_OwnedStringValue1`, `b`.`OwnedReference_OwnedStringValue2`, `o0`.`OwnedStringValue3`, `o`.`OwnedStringValue4`
+FROM `BaseEntity` AS `b`
+INNER JOIN `SiblingEntity` AS `s` ON `b`.`Id` = `s`.`Id`
+LEFT JOIN `OwnedReferencePart4` AS `o` ON `b`.`Id` = `o`.`BaseEntityId`
+LEFT JOIN `OwnedReferencePart3` AS `o0` ON `b`.`Id` = `o0`.`BaseEntityId`
+""");
+ }
+
+ public override async Task Tph_entity_owning_a_split_reference_on_middle_with_table_sharing_querying_sibling(bool async)
+ {
+ await base.Tph_entity_owning_a_split_reference_on_middle_with_table_sharing_querying_sibling(async);
+
+ AssertSql(
+"""
+SELECT `b`.`Id`, `b`.`BaseValue`, `b`.`Discriminator`, `b`.`SiblingValue`
+FROM `BaseEntity` AS `b`
+WHERE `b`.`Discriminator` = 'SiblingEntity'
+""");
+ }
+
+ public override async Task Tpt_entity_owning_a_split_reference_on_middle_with_table_sharing_querying_sibling(bool async)
+ {
+ await base.Tpt_entity_owning_a_split_reference_on_middle_with_table_sharing_querying_sibling(async);
+
+ AssertSql(
+"""
+SELECT `b`.`Id`, `b`.`BaseValue`, `s`.`SiblingValue`
+FROM `BaseEntity` AS `b`
+INNER JOIN `SiblingEntity` AS `s` ON `b`.`Id` = `s`.`Id`
+""");
+ }
+
+ public override async Task Tph_entity_owning_a_split_reference_on_leaf_with_table_sharing_querying_sibling(bool async)
+ {
+ await base.Tph_entity_owning_a_split_reference_on_leaf_with_table_sharing_querying_sibling(async);
+
+ AssertSql(
+"""
+SELECT `b`.`Id`, `b`.`BaseValue`, `b`.`Discriminator`, `b`.`SiblingValue`
+FROM `BaseEntity` AS `b`
+WHERE `b`.`Discriminator` = 'SiblingEntity'
+""");
+ }
+
+ public override async Task Tpt_entity_owning_a_split_reference_on_leaf_with_table_sharing_querying_sibling(bool async)
+ {
+ await base.Tpt_entity_owning_a_split_reference_on_leaf_with_table_sharing_querying_sibling(async);
+
+ AssertSql(
+"""
+SELECT `b`.`Id`, `b`.`BaseValue`, `s`.`SiblingValue`
+FROM `BaseEntity` AS `b`
+INNER JOIN `SiblingEntity` AS `s` ON `b`.`Id` = `s`.`Id`
+""");
+ }
+
+ public override async Task Tpc_entity_owning_a_split_reference_on_leaf_with_table_sharing_querying_sibling(bool async)
+ {
+ await base.Tpc_entity_owning_a_split_reference_on_leaf_with_table_sharing_querying_sibling(async);
+
+ AssertSql(
+"""
+SELECT `s`.`Id`, `s`.`BaseValue`, `s`.`SiblingValue`
+FROM `SiblingEntity` AS `s`
+""");
+ }
+
+ [ConditionalTheory(Skip = "Issue29075")]
+ public override async Task Tph_entity_owning_a_split_reference_on_base_without_table_sharing(bool async)
+ {
+ await base.Tph_entity_owning_a_split_reference_on_base_without_table_sharing(async);
+
+ AssertSql();
+ }
+
+ [ConditionalTheory(Skip = "Issue29075")]
+ public override async Task Tpt_entity_owning_a_split_reference_on_base_without_table_sharing(bool async)
+ {
+ await base.Tpt_entity_owning_a_split_reference_on_base_without_table_sharing(async);
+
+ AssertSql();
+ }
+
+ public override async Task Tpc_entity_owning_a_split_reference_on_base_without_table_sharing(bool async)
+ {
+ await base.Tpc_entity_owning_a_split_reference_on_base_without_table_sharing(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`BaseValue`, `t`.`MiddleValue`, `t`.`SiblingValue`, `t`.`LeafValue`, `t`.`Discriminator`, `o`.`BaseEntityId`, `o`.`Id`, `o`.`OwnedIntValue1`, `o`.`OwnedIntValue2`, `o1`.`OwnedIntValue3`, `o0`.`OwnedIntValue4`, `o`.`OwnedStringValue1`, `o`.`OwnedStringValue2`, `o1`.`OwnedStringValue3`, `o0`.`OwnedStringValue4`
+FROM (
+ SELECT `b`.`Id`, `b`.`BaseValue`, NULL AS `MiddleValue`, NULL AS `SiblingValue`, NULL AS `LeafValue`, 'BaseEntity' AS `Discriminator`
+ FROM `BaseEntity` AS `b`
+ UNION ALL
+ SELECT `m`.`Id`, `m`.`BaseValue`, `m`.`MiddleValue`, NULL AS `SiblingValue`, NULL AS `LeafValue`, 'MiddleEntity' AS `Discriminator`
+ FROM `MiddleEntity` AS `m`
+ UNION ALL
+ SELECT `s`.`Id`, `s`.`BaseValue`, NULL AS `MiddleValue`, `s`.`SiblingValue`, NULL AS `LeafValue`, 'SiblingEntity' AS `Discriminator`
+ FROM `SiblingEntity` AS `s`
+ UNION ALL
+ SELECT `l`.`Id`, `l`.`BaseValue`, `l`.`MiddleValue`, NULL AS `SiblingValue`, `l`.`LeafValue`, 'LeafEntity' AS `Discriminator`
+ FROM `LeafEntity` AS `l`
+) AS `t`
+LEFT JOIN `OwnedReferencePart1` AS `o` ON `t`.`Id` = `o`.`BaseEntityId`
+LEFT JOIN `OwnedReferencePart4` AS `o0` ON `o`.`BaseEntityId` = `o0`.`BaseEntityId`
+LEFT JOIN `OwnedReferencePart3` AS `o1` ON `o`.`BaseEntityId` = `o1`.`BaseEntityId`
+""");
+ }
+
+ [ConditionalTheory(Skip = "Issue29075")]
+ public override async Task Tph_entity_owning_a_split_reference_on_middle_without_table_sharing(bool async)
+ {
+ await base.Tph_entity_owning_a_split_reference_on_middle_without_table_sharing(async);
+
+ AssertSql();
+ }
+
+ [ConditionalTheory(Skip = "Issue29075")]
+ public override async Task Tpt_entity_owning_a_split_reference_on_middle_without_table_sharing(bool async)
+ {
+ await base.Tpt_entity_owning_a_split_reference_on_middle_without_table_sharing(async);
+
+ AssertSql();
+ }
+
+ public override async Task Tpc_entity_owning_a_split_reference_on_middle_without_table_sharing(bool async)
+ {
+ await base.Tpc_entity_owning_a_split_reference_on_middle_without_table_sharing(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`BaseValue`, `t`.`MiddleValue`, `t`.`SiblingValue`, `t`.`LeafValue`, `t`.`Discriminator`, `o`.`MiddleEntityId`, `o`.`Id`, `o`.`OwnedIntValue1`, `o`.`OwnedIntValue2`, `o1`.`OwnedIntValue3`, `o0`.`OwnedIntValue4`, `o`.`OwnedStringValue1`, `o`.`OwnedStringValue2`, `o1`.`OwnedStringValue3`, `o0`.`OwnedStringValue4`
+FROM (
+ SELECT `b`.`Id`, `b`.`BaseValue`, NULL AS `MiddleValue`, NULL AS `SiblingValue`, NULL AS `LeafValue`, 'BaseEntity' AS `Discriminator`
+ FROM `BaseEntity` AS `b`
+ UNION ALL
+ SELECT `m`.`Id`, `m`.`BaseValue`, `m`.`MiddleValue`, NULL AS `SiblingValue`, NULL AS `LeafValue`, 'MiddleEntity' AS `Discriminator`
+ FROM `MiddleEntity` AS `m`
+ UNION ALL
+ SELECT `s`.`Id`, `s`.`BaseValue`, NULL AS `MiddleValue`, `s`.`SiblingValue`, NULL AS `LeafValue`, 'SiblingEntity' AS `Discriminator`
+ FROM `SiblingEntity` AS `s`
+ UNION ALL
+ SELECT `l`.`Id`, `l`.`BaseValue`, `l`.`MiddleValue`, NULL AS `SiblingValue`, `l`.`LeafValue`, 'LeafEntity' AS `Discriminator`
+ FROM `LeafEntity` AS `l`
+) AS `t`
+LEFT JOIN `OwnedReferencePart1` AS `o` ON `t`.`Id` = `o`.`MiddleEntityId`
+LEFT JOIN `OwnedReferencePart4` AS `o0` ON `o`.`MiddleEntityId` = `o0`.`MiddleEntityId`
+LEFT JOIN `OwnedReferencePart3` AS `o1` ON `o`.`MiddleEntityId` = `o1`.`MiddleEntityId`
+""");
+ }
+
+ [ConditionalTheory(Skip = "Issue29075")]
+ public override async Task Tph_entity_owning_a_split_reference_on_leaf_without_table_sharing(bool async)
+ {
+ await base.Tph_entity_owning_a_split_reference_on_leaf_without_table_sharing(async);
+
+ AssertSql();
+ }
+
+ [ConditionalTheory(Skip = "Issue29075")]
+ public override async Task Tpt_entity_owning_a_split_reference_on_leaf_without_table_sharing(bool async)
+ {
+ await base.Tpt_entity_owning_a_split_reference_on_leaf_without_table_sharing(async);
+
+ AssertSql();
+ }
+
+ [ConditionalTheory(Skip = "Issue29075")]
+ public override async Task Tpc_entity_owning_a_split_reference_on_leaf_without_table_sharing(bool async)
+ {
+ await base.Tpc_entity_owning_a_split_reference_on_leaf_without_table_sharing(async);
+
+ AssertSql();
+ }
+
+ [ConditionalTheory(Skip = "Issue29075")]
+ public override async Task Tph_entity_owning_a_split_collection_on_base(bool async)
+ {
+ await base.Tph_entity_owning_a_split_collection_on_base(async);
+
+ AssertSql();
+ }
+
+ [ConditionalTheory(Skip = "Issue29075")]
+ public override async Task Tpt_entity_owning_a_split_collection_on_base(bool async)
+ {
+ await base.Tpt_entity_owning_a_split_collection_on_base(async);
+
+ AssertSql();
+ }
+
+ public override async Task Tpc_entity_owning_a_split_collection_on_base(bool async)
+ {
+ await base.Tpc_entity_owning_a_split_collection_on_base(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`BaseValue`, `t`.`MiddleValue`, `t`.`SiblingValue`, `t`.`LeafValue`, `t`.`Discriminator`, `t0`.`BaseEntityId`, `t0`.`Id`, `t0`.`OwnedIntValue1`, `t0`.`OwnedIntValue2`, `t0`.`OwnedIntValue3`, `t0`.`OwnedIntValue4`, `t0`.`OwnedStringValue1`, `t0`.`OwnedStringValue2`, `t0`.`OwnedStringValue3`, `t0`.`OwnedStringValue4`
+FROM (
+ SELECT `b`.`Id`, `b`.`BaseValue`, NULL AS `MiddleValue`, NULL AS `SiblingValue`, NULL AS `LeafValue`, 'BaseEntity' AS `Discriminator`
+ FROM `BaseEntity` AS `b`
+ UNION ALL
+ SELECT `m`.`Id`, `m`.`BaseValue`, `m`.`MiddleValue`, NULL AS `SiblingValue`, NULL AS `LeafValue`, 'MiddleEntity' AS `Discriminator`
+ FROM `MiddleEntity` AS `m`
+ UNION ALL
+ SELECT `s`.`Id`, `s`.`BaseValue`, NULL AS `MiddleValue`, `s`.`SiblingValue`, NULL AS `LeafValue`, 'SiblingEntity' AS `Discriminator`
+ FROM `SiblingEntity` AS `s`
+ UNION ALL
+ SELECT `l`.`Id`, `l`.`BaseValue`, `l`.`MiddleValue`, NULL AS `SiblingValue`, `l`.`LeafValue`, 'LeafEntity' AS `Discriminator`
+ FROM `LeafEntity` AS `l`
+) AS `t`
+LEFT JOIN (
+ SELECT `o`.`BaseEntityId`, `o`.`Id`, `o`.`OwnedIntValue1`, `o`.`OwnedIntValue2`, `o1`.`OwnedIntValue3`, `o0`.`OwnedIntValue4`, `o`.`OwnedStringValue1`, `o`.`OwnedStringValue2`, `o1`.`OwnedStringValue3`, `o0`.`OwnedStringValue4`
+ FROM `OwnedReferencePart1` AS `o`
+ INNER JOIN `OwnedReferencePart4` AS `o0` ON (`o`.`BaseEntityId` = `o0`.`BaseEntityId`) AND (`o`.`Id` = `o0`.`Id`)
+ INNER JOIN `OwnedReferencePart3` AS `o1` ON (`o`.`BaseEntityId` = `o1`.`BaseEntityId`) AND (`o`.`Id` = `o1`.`Id`)
+) AS `t0` ON `t`.`Id` = `t0`.`BaseEntityId`
+ORDER BY `t`.`Id`, `t0`.`BaseEntityId`
+""");
+ }
+
+ [ConditionalTheory(Skip = "Issue29075")]
+ public override async Task Tph_entity_owning_a_split_collection_on_middle(bool async)
+ {
+ await base.Tph_entity_owning_a_split_collection_on_middle(async);
+
+ AssertSql();
+ }
+
+ [ConditionalTheory(Skip = "Issue29075")]
+ public override async Task Tpt_entity_owning_a_split_collection_on_middle(bool async)
+ {
+ await base.Tpt_entity_owning_a_split_collection_on_middle(async);
+
+ AssertSql();
+ }
+
+ public override async Task Tpc_entity_owning_a_split_collection_on_middle(bool async)
+ {
+ await base.Tpc_entity_owning_a_split_collection_on_middle(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`BaseValue`, `t`.`MiddleValue`, `t`.`SiblingValue`, `t`.`LeafValue`, `t`.`Discriminator`, `t0`.`MiddleEntityId`, `t0`.`Id`, `t0`.`OwnedIntValue1`, `t0`.`OwnedIntValue2`, `t0`.`OwnedIntValue3`, `t0`.`OwnedIntValue4`, `t0`.`OwnedStringValue1`, `t0`.`OwnedStringValue2`, `t0`.`OwnedStringValue3`, `t0`.`OwnedStringValue4`
+FROM (
+ SELECT `b`.`Id`, `b`.`BaseValue`, NULL AS `MiddleValue`, NULL AS `SiblingValue`, NULL AS `LeafValue`, 'BaseEntity' AS `Discriminator`
+ FROM `BaseEntity` AS `b`
+ UNION ALL
+ SELECT `m`.`Id`, `m`.`BaseValue`, `m`.`MiddleValue`, NULL AS `SiblingValue`, NULL AS `LeafValue`, 'MiddleEntity' AS `Discriminator`
+ FROM `MiddleEntity` AS `m`
+ UNION ALL
+ SELECT `s`.`Id`, `s`.`BaseValue`, NULL AS `MiddleValue`, `s`.`SiblingValue`, NULL AS `LeafValue`, 'SiblingEntity' AS `Discriminator`
+ FROM `SiblingEntity` AS `s`
+ UNION ALL
+ SELECT `l`.`Id`, `l`.`BaseValue`, `l`.`MiddleValue`, NULL AS `SiblingValue`, `l`.`LeafValue`, 'LeafEntity' AS `Discriminator`
+ FROM `LeafEntity` AS `l`
+) AS `t`
+LEFT JOIN (
+ SELECT `o`.`MiddleEntityId`, `o`.`Id`, `o`.`OwnedIntValue1`, `o`.`OwnedIntValue2`, `o1`.`OwnedIntValue3`, `o0`.`OwnedIntValue4`, `o`.`OwnedStringValue1`, `o`.`OwnedStringValue2`, `o1`.`OwnedStringValue3`, `o0`.`OwnedStringValue4`
+ FROM `OwnedReferencePart1` AS `o`
+ INNER JOIN `OwnedReferencePart4` AS `o0` ON (`o`.`MiddleEntityId` = `o0`.`MiddleEntityId`) AND (`o`.`Id` = `o0`.`Id`)
+ INNER JOIN `OwnedReferencePart3` AS `o1` ON (`o`.`MiddleEntityId` = `o1`.`MiddleEntityId`) AND (`o`.`Id` = `o1`.`Id`)
+) AS `t0` ON `t`.`Id` = `t0`.`MiddleEntityId`
+ORDER BY `t`.`Id`, `t0`.`MiddleEntityId`
+""");
+ }
+
+ [ConditionalTheory(Skip = "Issue29075")]
+ public override async Task Tph_entity_owning_a_split_collection_on_leaf(bool async)
+ {
+ await base.Tph_entity_owning_a_split_collection_on_leaf(async);
+
+ AssertSql();
+ }
+
+ [ConditionalTheory(Skip = "Issue29075")]
+ public override async Task Tpt_entity_owning_a_split_collection_on_leaf(bool async)
+ {
+ await base.Tpt_entity_owning_a_split_collection_on_leaf(async);
+
+ AssertSql();
+ }
+
+ [ConditionalTheory(Skip = "Issue29075")]
+ public override async Task Tpc_entity_owning_a_split_collection_on_leaf(bool async)
+ {
+ await base.Tpc_entity_owning_a_split_collection_on_leaf(async);
+
+ AssertSql();
+ }
+
+ protected override ITestStoreFactory TestStoreFactory => MySqlTestStoreFactory.Instance;
+}
diff --git a/test/EFCore.MySql.FunctionalTests/Query/EscapesMySqlNoBackslashesTest.cs b/test/EFCore.MySql.FunctionalTests/Query/EscapesMySqlNoBackslashesTest.cs
index 6e6f5f0a7..1fe0e3da1 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/EscapesMySqlNoBackslashesTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/EscapesMySqlNoBackslashesTest.cs
@@ -1,6 +1,7 @@
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Pomelo.EntityFrameworkCore.MySql.FunctionalTests.TestUtilities;
+using Pomelo.EntityFrameworkCore.MySql.Tests;
using Xunit;
using Xunit.Abstractions;
@@ -19,18 +20,34 @@ public override void Input_query_escapes_parameter()
{
base.Input_query_escapes_parameter();
- AssertSql(
- @"@p0='Back\slash's Garden Party' (Nullable = false) (Size = 4000)
+ if (AppConfig.ServerVersion.Supports.Returning)
+ {
+ AssertSql(
+ @"@p0='Back\slash's Garden Party' (Nullable = false) (Size = 4000)
+
+INSERT INTO `Artists` (`Name`)
+VALUES (@p0)
+RETURNING `ArtistId`;",
+ //
+ @"SELECT `a`.`ArtistId`, `a`.`Name`
+FROM `Artists` AS `a`
+WHERE `a`.`Name` LIKE '% Garden Party'");
+ }
+ else
+ {
+ AssertSql(
+ @"@p0='Back\slash's Garden Party' (Nullable = false) (Size = 4000)
INSERT INTO `Artists` (`Name`)
VALUES (@p0);
SELECT `ArtistId`
FROM `Artists`
WHERE ROW_COUNT() = 1 AND `ArtistId` = LAST_INSERT_ID();",
- //
- @"SELECT `a`.`ArtistId`, `a`.`Name`
+ //
+ @"SELECT `a`.`ArtistId`, `a`.`Name`
FROM `Artists` AS `a`
WHERE `a`.`Name` LIKE '% Garden Party'");
+ }
}
[ConditionalTheory]
diff --git a/test/EFCore.MySql.FunctionalTests/Query/EscapesMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/EscapesMySqlTest.cs
index de60a41c8..20792a5d2 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/EscapesMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/EscapesMySqlTest.cs
@@ -1,6 +1,7 @@
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Pomelo.EntityFrameworkCore.MySql.FunctionalTests.TestUtilities;
+using Pomelo.EntityFrameworkCore.MySql.Tests;
using Xunit;
using Xunit.Abstractions;
@@ -19,18 +20,34 @@ public override void Input_query_escapes_parameter()
{
base.Input_query_escapes_parameter();
- AssertSql(
- @"@p0='Back\slash's Garden Party' (Nullable = false) (Size = 4000)
+ if (AppConfig.ServerVersion.Supports.Returning)
+ {
+ AssertSql(
+ @"@p0='Back\slash's Garden Party' (Nullable = false) (Size = 4000)
+
+INSERT INTO `Artists` (`Name`)
+VALUES (@p0)
+RETURNING `ArtistId`;",
+ //
+ @"SELECT `a`.`ArtistId`, `a`.`Name`
+FROM `Artists` AS `a`
+WHERE `a`.`Name` LIKE '% Garden Party'");
+ }
+ else
+ {
+ AssertSql(
+ @"@p0='Back\slash's Garden Party' (Nullable = false) (Size = 4000)
INSERT INTO `Artists` (`Name`)
VALUES (@p0);
SELECT `ArtistId`
FROM `Artists`
WHERE ROW_COUNT() = 1 AND `ArtistId` = LAST_INSERT_ID();",
- //
- @"SELECT `a`.`ArtistId`, `a`.`Name`
+ //
+ @"SELECT `a`.`ArtistId`, `a`.`Name`
FROM `Artists` AS `a`
WHERE `a`.`Name` LIKE '% Garden Party'");
+ }
}
[ConditionalTheory]
diff --git a/test/EFCore.MySql.FunctionalTests/Query/FromSqlQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/FromSqlQueryMySqlTest.cs
index ec3ddc030..9db9490d8 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/FromSqlQueryMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/FromSqlQueryMySqlTest.cs
@@ -3,6 +3,8 @@
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.TestUtilities;
using MySqlConnector;
+using Pomelo.EntityFrameworkCore.MySql.Infrastructure;
+using Pomelo.EntityFrameworkCore.MySql.Tests.TestUtilities.Attributes;
using Xunit;
namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests.Query
@@ -14,6 +16,12 @@ public FromSqlQueryMySqlTest(NorthwindQueryMySqlFixture fix
{
}
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.CommonTableExpressions))]
+ public override Task FromSqlRaw_composed_with_common_table_expression(bool async)
+ {
+ return base.FromSqlRaw_composed_with_common_table_expression(async);
+ }
+
protected override DbParameter CreateDbParameter(string name, object value)
=> new MySqlParameter
{
diff --git a/test/EFCore.MySql.FunctionalTests/Query/GearsOfWarQueryMySqlFixture.cs b/test/EFCore.MySql.FunctionalTests/Query/GearsOfWarQueryMySqlFixture.cs
index cd91b79aa..adfdef379 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/GearsOfWarQueryMySqlFixture.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/GearsOfWarQueryMySqlFixture.cs
@@ -1,4 +1,3 @@
-using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Pomelo.EntityFrameworkCore.MySql.FunctionalTests.TestUtilities;
@@ -35,22 +34,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
foreach (var mission in data.Missions)
{
- mission.Timeline = GetExpectedValue(mission.Timeline);
+ mission.Timeline = MySqlTestHelpers.GetExpectedValue(mission.Timeline);
}
return data;
}
-
- public static DateTimeOffset GetExpectedValue(DateTimeOffset value)
- {
- const int mySqlMaxMillisecondDecimalPlaces = 6;
- var decimalPlacesFactor = (decimal)Math.Pow(10, 7 - mySqlMaxMillisecondDecimalPlaces);
-
- // Change DateTimeOffset values, because MySQL does not preserve offsets and has a maximum of 6 decimal places, in contrast to
- // .NET which has 7.
- return new DateTimeOffset(
- (long)(Math.Truncate(value.UtcTicks / decimalPlacesFactor) * decimalPlacesFactor),
- TimeSpan.Zero);
- }
}
}
diff --git a/test/EFCore.MySql.FunctionalTests/Query/GearsOfWarQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/GearsOfWarQueryMySqlTest.cs
index 13ee566a0..046e59943 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/GearsOfWarQueryMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/GearsOfWarQueryMySqlTest.cs
@@ -4,6 +4,7 @@
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.TestModels.GearsOfWarModel;
+using Pomelo.EntityFrameworkCore.MySql.FunctionalTests.TestUtilities;
using Pomelo.EntityFrameworkCore.MySql.Infrastructure;
using Pomelo.EntityFrameworkCore.MySql.Tests.TestUtilities.Attributes;
using Xunit;
@@ -25,7 +26,7 @@ protected override bool CanExecuteQueryString
public override Task DateTimeOffset_Contains_Less_than_Greater_than(bool async)
{
- var dto = GearsOfWarQueryMySqlFixture.GetExpectedValue(new DateTimeOffset(599898024001234567, new TimeSpan(1, 30, 0)));
+ var dto = MySqlTestHelpers.GetExpectedValue(new DateTimeOffset(599898024001234567, new TimeSpan(1, 30, 0)));
var start = dto.AddDays(-1);
var end = dto.AddDays(1);
var dates = new[] { dto };
@@ -38,7 +39,7 @@ public override Task DateTimeOffset_Contains_Less_than_Greater_than(bool async)
public override Task Where_datetimeoffset_milliseconds_parameter_and_constant(bool async)
{
- var dateTimeOffset = GearsOfWarQueryMySqlFixture.GetExpectedValue(new DateTimeOffset(599898024001234567, new TimeSpan(1, 30, 0)));
+ var dateTimeOffset = MySqlTestHelpers.GetExpectedValue(new DateTimeOffset(599898024001234567, new TimeSpan(1, 30, 0)));
// Literal where clause
var p = Expression.Parameter(typeof(Mission), "i");
@@ -54,6 +55,25 @@ public override Task Where_datetimeoffset_milliseconds_parameter_and_constant(bo
ss => ss.Set().Where(m => m.Timeline == dateTimeOffset));
}
+ [ConditionalTheory(Skip = "TODO: Does not work as expected, probably due to some test definition issues.")]
+ public override async Task DateTimeOffsetNow_minus_timespan(bool async)
+ {
+ var timeSpan = new TimeSpan(10000); // <-- changed from 1000 to 10000 ticks
+
+ await AssertQuery(
+ async,
+ ss => ss.Set().Where(e => e.Timeline > DateTimeOffset.Now - timeSpan));
+
+ AssertSql(
+"""
+@__timeSpan_0='00:00:00.0010000' (DbType = DateTimeOffset)
+
+SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline`
+FROM `Missions` AS `m`
+WHERE `m`.`Timeline` > (UTC_TIMESTAMP() - @__timeSpan_0)
+""");
+ }
+
// TODO: Implement strategy as discussed with @roji (including emails) for EF Core 5.
[ConditionalTheory(Skip = "#996")]
public override Task Client_member_and_unsupported_string_Equals_in_the_same_query(bool async)
@@ -210,5 +230,12 @@ public override Task Subquery_projecting_non_nullable_scalar_contains_non_nullab
{
return base.Subquery_projecting_non_nullable_scalar_contains_non_nullable_value_doesnt_need_null_expansion(async);
}
+
+ [ConditionalTheory(Skip = "Another LATERAL JOIN bug in MySQL. Grouping leads to unexpected result set.")]
+ [MemberData(nameof(IsAsyncData))]
+ public override Task Correlated_collection_with_groupby_with_complex_grouping_key_not_projecting_identifier_column_with_group_aggregate_in_final_projection(bool async)
+ {
+ return base.Correlated_collection_with_groupby_with_complex_grouping_key_not_projecting_identifier_column_with_group_aggregate_in_final_projection(async);
+ }
}
}
diff --git a/test/EFCore.MySql.FunctionalTests/Query/MatchQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/MatchQueryMySqlTest.cs
index 657f64344..78f9b2e57 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/MatchQueryMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/MatchQueryMySqlTest.cs
@@ -320,13 +320,11 @@ public Func GetContextCreator()
public ISetSource GetExpectedData()
=> new MatchQueryData();
- public IReadOnlyDictionary GetEntitySorters()
- => new Dictionary>
- {
- { typeof(Herb), e => ((Herb)e)?.Id },
- }.ToDictionary(e => e.Key, e => (object)e.Value);
+ public IReadOnlyDictionary EntitySorters
+ => new Dictionary> { { typeof(Herb), e => ((Herb)e)?.Id }, }.ToDictionary(e => e.Key,
+ e => (object)e.Value);
- public IReadOnlyDictionary GetEntityAsserters()
+ public IReadOnlyDictionary EntityAsserters
=> new Dictionary>
{
{
diff --git a/test/EFCore.MySql.FunctionalTests/Query/NorthwindAggregateOperatorsQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/NorthwindAggregateOperatorsQueryMySqlTest.cs
index 0ebd1a124..6ec051935 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/NorthwindAggregateOperatorsQueryMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/NorthwindAggregateOperatorsQueryMySqlTest.cs
@@ -1,5 +1,9 @@
-using Microsoft.EntityFrameworkCore.Query;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore.Query;
+using Microsoft.EntityFrameworkCore.TestModels.Northwind;
using Microsoft.EntityFrameworkCore.TestUtilities;
+using Xunit;
using Xunit.Abstractions;
namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests.Query
@@ -16,6 +20,31 @@ public NorthwindAggregateOperatorsQueryMySqlTest(
//Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}
+ public override Task Average_over_max_subquery_is_client_eval(bool async)
+ => AssertAverage(
+ async,
+ ss => ss.Set().OrderBy(c => c.CustomerID).Take(3),
+ selector: c => (decimal)c.Orders.Average(o => 5 + o.OrderDetails.Max(od => od.ProductID)),
+ asserter: (a, b) => Assert.Equal(a, b, 12)); // added flouting point precision tolerance
+
+ public override Task Average_over_nested_subquery_is_client_eval(bool async)
+ => AssertAverage(
+ async,
+ ss => ss.Set().OrderBy(c => c.CustomerID).Take(3),
+ selector: c => (decimal)c.Orders.Average(o => 5 + o.OrderDetails.Average(od => od.ProductID)),
+ asserter: (a, b) => Assert.Equal(a, b, 12)); // added flouting point precision tolerance
+
+ public override async Task Contains_with_local_anonymous_type_array_closure(bool async)
+ {
+ // Aggregates. Issue #15937.
+ await AssertTranslationFailed(() => base.Contains_with_local_anonymous_type_array_closure(async));
+
+ AssertSql();
+ }
+
+ public override async Task Contains_with_local_tuple_array_closure(bool async)
+ => await AssertTranslationFailed(() => base.Contains_with_local_tuple_array_closure(async));
+
protected override bool CanExecuteQueryString
=> true;
diff --git a/test/EFCore.MySql.FunctionalTests/Query/NorthwindAggregateQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/NorthwindAggregateQueryMySqlTest.cs
index d55d1e60e..28de881f9 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/NorthwindAggregateQueryMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/NorthwindAggregateQueryMySqlTest.cs
@@ -1,6 +1,10 @@
-using System.Threading.Tasks;
+using System;
+using System.Linq;
+using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Query;
+using Microsoft.EntityFrameworkCore.TestModels.Northwind;
using Microsoft.EntityFrameworkCore.TestUtilities;
+using Pomelo.EntityFrameworkCore.MySql.FunctionalTests.TestUtilities;
using Xunit;
using Xunit.Abstractions;
@@ -18,7 +22,6 @@ public NorthwindAggregateQueryMySqlTest(
//Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}
- [ConditionalTheory]
public override async Task Sum_with_coalesce(bool async)
{
await base.Sum_with_coalesce(async);
@@ -29,6 +32,69 @@ public override async Task Sum_with_coalesce(bool async)
WHERE `p`.`ProductID` < 40");
}
+ public override async Task Average_over_max_subquery_is_client_eval(bool async)
+ {
+ await AssertAverage(
+ async,
+ ss => ss.Set().OrderBy(c => c.CustomerID).Take(3),
+ selector: c => (decimal)c.Orders.Average(o => 5 + o.OrderDetails.Max(od => od.ProductID)),
+ asserter: (a, b) => Assert.Equal(a, b, 12)); // added flouting point precision tolerance
+
+ AssertSql(
+ $@"@__p_0='3'
+
+SELECT AVG(CAST((
+ SELECT AVG({MySqlTestHelpers.CastAsDouble(@"5 + (
+ SELECT MAX(`o0`.`ProductID`)
+ FROM `Order Details` AS `o0`
+ WHERE `o`.`OrderID` = `o0`.`OrderID`)")})
+ FROM `Orders` AS `o`
+ WHERE `t`.`CustomerID` = `o`.`CustomerID`) AS decimal(65,30)))
+FROM (
+ SELECT `c`.`CustomerID`
+ FROM `Customers` AS `c`
+ ORDER BY `c`.`CustomerID`
+ LIMIT @__p_0
+) AS `t`");
+ }
+
+ public override async Task Average_over_nested_subquery_is_client_eval(bool async)
+ {
+ await AssertAverage(
+ async,
+ ss => ss.Set().OrderBy(c => c.CustomerID).Take(3),
+ selector: c => (decimal)c.Orders.Average(o => 5 + o.OrderDetails.Average(od => od.ProductID)),
+ asserter: (a, b) => Assert.Equal(a, b, 12)); // added flouting point precision tolerance
+
+ AssertSql(
+ $@"@__p_0='3'
+
+SELECT AVG(CAST((
+ SELECT AVG(5.0 + (
+ SELECT AVG({MySqlTestHelpers.CastAsDouble(@"`o0`.`ProductID`")})
+ FROM `Order Details` AS `o0`
+ WHERE `o`.`OrderID` = `o0`.`OrderID`))
+ FROM `Orders` AS `o`
+ WHERE `t`.`CustomerID` = `o`.`CustomerID`) AS decimal(65,30)))
+FROM (
+ SELECT `c`.`CustomerID`
+ FROM `Customers` AS `c`
+ ORDER BY `c`.`CustomerID`
+ LIMIT @__p_0
+) AS `t`");
+ }
+
+ public override async Task Contains_with_local_anonymous_type_array_closure(bool async)
+ {
+ // Aggregates. Issue #15937.
+ await AssertTranslationFailed(() => base.Contains_with_local_anonymous_type_array_closure(async));
+
+ AssertSql();
+ }
+
+ public override async Task Contains_with_local_tuple_array_closure(bool async)
+ => await AssertTranslationFailed(() => base.Contains_with_local_tuple_array_closure(async));
+
private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
diff --git a/test/EFCore.MySql.FunctionalTests/Query/NorthwindEFPropertyIncludeQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/NorthwindEFPropertyIncludeQueryMySqlTest.cs
new file mode 100644
index 000000000..61e89c28a
--- /dev/null
+++ b/test/EFCore.MySql.FunctionalTests/Query/NorthwindEFPropertyIncludeQueryMySqlTest.cs
@@ -0,0 +1,2297 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Diagnostics;
+using Microsoft.EntityFrameworkCore.Query;
+using Microsoft.EntityFrameworkCore.TestModels.Northwind;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+using Xunit;
+
+namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests.Query;
+
+public class NorthwindEFPropertyIncludeQueryMySqlTest : NorthwindEFPropertyIncludeQueryTestBase<
+ NorthwindQueryMySqlFixture>
+{
+ // ReSharper disable once UnusedParameter.Local
+ public NorthwindEFPropertyIncludeQueryMySqlTest(NorthwindQueryMySqlFixture fixture)
+ : base(fixture)
+ {
+ Fixture.TestSqlLoggerFactory.Clear();
+ }
+
+ [ConditionalFact]
+ public virtual void Check_all_tests_overridden()
+ => TestHelpers.AssertAllMethodsOverridden(GetType());
+
+ public override async Task Include_collection_with_last_no_orderby(bool async)
+ {
+ Assert.Equal(
+ RelationalStrings.LastUsedWithoutOrderBy(nameof(Enumerable.Last)),
+ (await Assert.ThrowsAsync(
+ () => base.Include_collection_with_last_no_orderby(async))).Message);
+
+ AssertSql();
+ }
+
+ public override async Task Include_collection_with_filter_reordered(bool async)
+ {
+ await base.Include_collection_with_filter_reordered(async);
+
+ AssertSql(
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+FROM `Customers` AS `c`
+LEFT JOIN `Orders` AS `o` ON `c`.`CustomerID` = `o`.`CustomerID`
+WHERE `c`.`CustomerID` = 'ALFKI'
+ORDER BY `c`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_collection_order_by_non_key_with_first_or_default(bool async)
+ {
+ await base.Include_collection_order_by_non_key_with_first_or_default(async);
+
+ AssertSql(
+"""
+SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+FROM (
+ 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`
+ ORDER BY `c`.`CompanyName` DESC
+ LIMIT 1
+) AS `t`
+LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID`
+ORDER BY `t`.`CompanyName` DESC, `t`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_with_cycle_does_not_throw_when_AsTracking_NoTrackingWithIdentityResolution(bool async)
+ {
+ await base.Include_with_cycle_does_not_throw_when_AsTracking_NoTrackingWithIdentityResolution(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`
+FROM `Orders` AS `o`
+LEFT JOIN `Customers` AS `c` ON `o`.`CustomerID` = `c`.`CustomerID`
+LEFT JOIN `Orders` AS `o0` ON `c`.`CustomerID` = `o0`.`CustomerID`
+WHERE `o`.`OrderID` < 10800
+ORDER BY `o`.`OrderID`, `c`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_collection_with_filter(bool async)
+ {
+ await base.Include_collection_with_filter(async);
+
+ AssertSql(
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+FROM `Customers` AS `c`
+LEFT JOIN `Orders` AS `o` ON `c`.`CustomerID` = `o`.`CustomerID`
+WHERE `c`.`CustomerID` = 'ALFKI'
+ORDER BY `c`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_references_then_include_multi_level(bool async)
+ {
+ await base.Include_references_then_include_multi_level(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`, `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+FROM `Order Details` AS `o`
+INNER JOIN `Orders` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+LEFT JOIN `Customers` AS `c` ON `o0`.`CustomerID` = `c`.`CustomerID`
+WHERE (`o`.`OrderID` % 23) = 13
+""");
+ }
+
+ public override async Task Include_collection_order_by_collection_column(bool async)
+ {
+ await base.Include_collection_order_by_collection_column(async);
+
+ AssertSql(
+"""
+SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`
+FROM (
+ SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, (
+ SELECT `o`.`OrderDate`
+ FROM `Orders` AS `o`
+ WHERE `c`.`CustomerID` = `o`.`CustomerID`
+ ORDER BY `o`.`OrderDate` DESC
+ LIMIT 1) AS `c`
+ FROM `Customers` AS `c`
+ WHERE `c`.`CustomerID` LIKE 'W%'
+ ORDER BY (
+ SELECT `o`.`OrderDate`
+ FROM `Orders` AS `o`
+ WHERE `c`.`CustomerID` = `o`.`CustomerID`
+ ORDER BY `o`.`OrderDate` DESC
+ LIMIT 1) DESC
+ LIMIT 1
+) AS `t`
+LEFT JOIN `Orders` AS `o0` ON `t`.`CustomerID` = `o0`.`CustomerID`
+ORDER BY `t`.`c` DESC, `t`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_collection_alias_generation(bool async)
+ {
+ await base.Include_collection_alias_generation(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, `o0`.`OrderID`, `o0`.`ProductID`, `o0`.`Discount`, `o0`.`Quantity`, `o0`.`UnitPrice`
+FROM `Orders` AS `o`
+LEFT JOIN `Order Details` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+WHERE `o`.`CustomerID` IS NOT NULL AND (`o`.`CustomerID` LIKE 'F%')
+ORDER BY `o`.`OrderID`, `o0`.`OrderID`
+""");
+ }
+
+ public override async Task Include_collection_skip_take_no_order_by(bool async)
+ {
+ await base.Include_collection_skip_take_no_order_by(async);
+
+ AssertSql(
+"""
+@__p_1='5'
+@__p_0='10'
+
+SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+FROM (
+ 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`
+ LIMIT @__p_1 OFFSET @__p_0
+) AS `t`
+LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID`
+ORDER BY `t`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_collection_with_cross_join_clause_with_filter(bool async)
+ {
+ await base.Include_collection_with_cross_join_clause_with_filter(async);
+
+ AssertSql(
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `t`.`OrderID`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`
+FROM `Customers` AS `c`
+CROSS JOIN (
+ SELECT `o`.`OrderID`
+ FROM `Orders` AS `o`
+ ORDER BY `o`.`OrderID`
+ LIMIT 5
+) AS `t`
+LEFT JOIN `Orders` AS `o0` ON `c`.`CustomerID` = `o0`.`CustomerID`
+WHERE `c`.`CustomerID` LIKE 'F%'
+ORDER BY `c`.`CustomerID`, `t`.`OrderID`
+""");
+ }
+
+ public override async Task Join_Include_reference_GroupBy_Select(bool async)
+ {
+ await base.Join_Include_reference_GroupBy_Select(async);
+
+ AssertSql(
+"""
+SELECT `t0`.`OrderID`, `t0`.`CustomerID`, `t0`.`EmployeeID`, `t0`.`OrderDate`, `t0`.`CustomerID0`, `t0`.`Address`, `t0`.`City`, `t0`.`CompanyName`, `t0`.`ContactName`, `t0`.`ContactTitle`, `t0`.`Country`, `t0`.`Fax`, `t0`.`Phone`, `t0`.`PostalCode`, `t0`.`Region`
+FROM (
+ SELECT `o0`.`OrderID`
+ FROM `Order Details` AS `o`
+ INNER JOIN `Orders` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+ GROUP BY `o0`.`OrderID`
+) AS `t`
+LEFT JOIN (
+ SELECT `t1`.`OrderID`, `t1`.`CustomerID`, `t1`.`EmployeeID`, `t1`.`OrderDate`, `t1`.`CustomerID0`, `t1`.`Address`, `t1`.`City`, `t1`.`CompanyName`, `t1`.`ContactName`, `t1`.`ContactTitle`, `t1`.`Country`, `t1`.`Fax`, `t1`.`Phone`, `t1`.`PostalCode`, `t1`.`Region`
+ FROM (
+ SELECT `o2`.`OrderID`, `o2`.`CustomerID`, `o2`.`EmployeeID`, `o2`.`OrderDate`, `c`.`CustomerID` AS `CustomerID0`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, ROW_NUMBER() OVER(PARTITION BY `o2`.`OrderID` ORDER BY `o2`.`OrderID`) AS `row`
+ FROM `Order Details` AS `o1`
+ INNER JOIN `Orders` AS `o2` ON `o1`.`OrderID` = `o2`.`OrderID`
+ LEFT JOIN `Customers` AS `c` ON `o2`.`CustomerID` = `c`.`CustomerID`
+ ) AS `t1`
+ WHERE `t1`.`row` <= 1
+) AS `t0` ON `t`.`OrderID` = `t0`.`OrderID`
+""");
+ }
+
+ public override async Task Include_multi_level_reference_and_collection_predicate(bool async)
+ {
+ await base.Include_multi_level_reference_and_collection_predicate(async);
+
+ AssertSql(
+"""
+SELECT `t`.`OrderID`, `t`.`CustomerID`, `t`.`EmployeeID`, `t`.`OrderDate`, `t`.`CustomerID0`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`
+FROM (
+ SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, `c`.`CustomerID` AS `CustomerID0`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+ FROM `Orders` AS `o`
+ LEFT JOIN `Customers` AS `c` ON `o`.`CustomerID` = `c`.`CustomerID`
+ WHERE `o`.`OrderID` = 10248
+ LIMIT 2
+) AS `t`
+LEFT JOIN `Orders` AS `o0` ON `t`.`CustomerID0` = `o0`.`CustomerID`
+ORDER BY `t`.`OrderID`, `t`.`CustomerID0`
+""");
+ }
+
+ public override async Task Include_references_then_include_collection(bool async)
+ {
+ await base.Include_references_then_include_collection(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`
+FROM `Orders` AS `o`
+LEFT JOIN `Customers` AS `c` ON `o`.`CustomerID` = `c`.`CustomerID`
+LEFT JOIN `Orders` AS `o0` ON `c`.`CustomerID` = `o0`.`CustomerID`
+WHERE `o`.`CustomerID` IS NOT NULL AND (`o`.`CustomerID` LIKE 'F%')
+ORDER BY `o`.`OrderID`, `c`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_collection_on_additional_from_clause_with_filter(bool async)
+ {
+ await base.Include_collection_on_additional_from_clause_with_filter(async);
+
+ AssertSql(
+"""
+SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `c`.`CustomerID`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+FROM `Customers` AS `c`
+CROSS JOIN (
+ SELECT `c0`.`CustomerID`, `c0`.`Address`, `c0`.`City`, `c0`.`CompanyName`, `c0`.`ContactName`, `c0`.`ContactTitle`, `c0`.`Country`, `c0`.`Fax`, `c0`.`Phone`, `c0`.`PostalCode`, `c0`.`Region`
+ FROM `Customers` AS `c0`
+ WHERE `c0`.`CustomerID` = 'ALFKI'
+) AS `t`
+LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID`
+ORDER BY `c`.`CustomerID`, `t`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_duplicate_reference3(bool async)
+ {
+ await base.Include_duplicate_reference3(async);
+
+ AssertSql(
+"""
+@__p_0='2'
+
+SELECT `t`.`OrderID`, `t`.`CustomerID`, `t`.`EmployeeID`, `t`.`OrderDate`, `t0`.`OrderID`, `t0`.`CustomerID`, `t0`.`EmployeeID`, `t0`.`OrderDate`, `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+FROM (
+ SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+ FROM `Orders` AS `o`
+ ORDER BY `o`.`OrderID`
+ LIMIT @__p_0
+) AS `t`
+CROSS JOIN (
+ SELECT `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`
+ FROM `Orders` AS `o0`
+ ORDER BY `o0`.`OrderID`
+ LIMIT 2 OFFSET 2
+) AS `t0`
+LEFT JOIN `Customers` AS `c` ON `t0`.`CustomerID` = `c`.`CustomerID`
+ORDER BY `t`.`OrderID`
+""");
+ }
+
+ public override async Task Include_collection_order_by_non_key_with_take(bool async)
+ {
+ await base.Include_collection_order_by_non_key_with_take(async);
+
+ AssertSql(
+"""
+@__p_0='10'
+
+SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+FROM (
+ 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`
+ ORDER BY `c`.`ContactTitle`
+ LIMIT @__p_0
+) AS `t`
+LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID`
+ORDER BY `t`.`ContactTitle`, `t`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_collection_then_include_collection_predicate(bool async)
+ {
+ await base.Include_collection_then_include_collection_predicate(async);
+
+ AssertSql(
+"""
+SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `t0`.`OrderID`, `t0`.`CustomerID`, `t0`.`EmployeeID`, `t0`.`OrderDate`, `t0`.`OrderID0`, `t0`.`ProductID`, `t0`.`Discount`, `t0`.`Quantity`, `t0`.`UnitPrice`
+FROM (
+ 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`.`CustomerID` = 'ALFKI'
+ LIMIT 2
+) AS `t`
+LEFT JOIN (
+ SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, `o0`.`OrderID` AS `OrderID0`, `o0`.`ProductID`, `o0`.`Discount`, `o0`.`Quantity`, `o0`.`UnitPrice`
+ FROM `Orders` AS `o`
+ LEFT JOIN `Order Details` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+) AS `t0` ON `t`.`CustomerID` = `t0`.`CustomerID`
+ORDER BY `t`.`CustomerID`, `t0`.`OrderID`, `t0`.`OrderID0`
+""");
+ }
+
+ public override async Task Include_collection_take_no_order_by(bool async)
+ {
+ await base.Include_collection_take_no_order_by(async);
+
+ AssertSql(
+"""
+@__p_0='10'
+
+SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+FROM (
+ 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`
+ LIMIT @__p_0
+) AS `t`
+LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID`
+ORDER BY `t`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_collection_principal_already_tracked(bool async)
+ {
+ await base.Include_collection_principal_already_tracked(async);
+
+ AssertSql(
+"""
+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`.`CustomerID` = 'ALFKI'
+LIMIT 2
+""",
+ //
+ """
+SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+FROM (
+ 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`.`CustomerID` = 'ALFKI'
+ LIMIT 2
+) AS `t`
+LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID`
+ORDER BY `t`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_collection_OrderBy_object(bool async)
+ {
+ await base.Include_collection_OrderBy_object(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, `o0`.`OrderID`, `o0`.`ProductID`, `o0`.`Discount`, `o0`.`Quantity`, `o0`.`UnitPrice`
+FROM `Orders` AS `o`
+LEFT JOIN `Order Details` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+WHERE `o`.`OrderID` < 10250
+ORDER BY `o`.`OrderID`, `o0`.`OrderID`
+""");
+ }
+
+ public override async Task Include_duplicate_collection_result_operator2(bool async)
+ {
+ // The order of `Orders` can be different, because it is not explicitly sorted.
+ // The order of the end result can be different as well.
+ // This is the case on MariaDB.
+ await AssertQuery(
+ async,
+ ss => (from c1 in ss.Set().Include(c => c.Orders).OrderBy(c => c.CustomerID).ThenBy(c => c.Orders.FirstOrDefault() != null ? c.Orders.FirstOrDefault().CustomerID : null).Take(2)
+ from c2 in ss.Set().OrderBy(c => c.CustomerID).Skip(2).Take(2)
+ select new { c1, c2 }).OrderBy(t => t.c1.CustomerID).ThenBy(t => t.c2.CustomerID).Take(1),
+ elementSorter: e => (e.c1.CustomerID, e.c2.CustomerID),
+ elementAsserter: (e, a) =>
+ {
+ AssertInclude(e.c1, a.c1, new ExpectedInclude(c => c.Orders));
+ AssertEqual(e.c2, a.c2);
+ },
+ entryCount: 8);
+
+ AssertSql(
+"""
+@__p_0='2'
+@__p_1='1'
+
+SELECT `t1`.`CustomerID`, `t1`.`Address`, `t1`.`City`, `t1`.`CompanyName`, `t1`.`ContactName`, `t1`.`ContactTitle`, `t1`.`Country`, `t1`.`Fax`, `t1`.`Phone`, `t1`.`PostalCode`, `t1`.`Region`, `t1`.`CustomerID0`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`, `t1`.`Address0`, `t1`.`City0`, `t1`.`CompanyName0`, `t1`.`ContactName0`, `t1`.`ContactTitle0`, `t1`.`Country0`, `t1`.`Fax0`, `t1`.`Phone0`, `t1`.`PostalCode0`, `t1`.`Region0`
+FROM (
+ SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `t0`.`CustomerID` AS `CustomerID0`, `t0`.`Address` AS `Address0`, `t0`.`City` AS `City0`, `t0`.`CompanyName` AS `CompanyName0`, `t0`.`ContactName` AS `ContactName0`, `t0`.`ContactTitle` AS `ContactTitle0`, `t0`.`Country` AS `Country0`, `t0`.`Fax` AS `Fax0`, `t0`.`Phone` AS `Phone0`, `t0`.`PostalCode` AS `PostalCode0`, `t0`.`Region` AS `Region0`
+ FROM (
+ 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`
+ ORDER BY `c`.`CustomerID`, (
+ SELECT `o`.`CustomerID`
+ FROM `Orders` AS `o`
+ WHERE `c`.`CustomerID` = `o`.`CustomerID`
+ LIMIT 1)
+ LIMIT @__p_0
+ ) AS `t`
+ CROSS JOIN (
+ SELECT `c0`.`CustomerID`, `c0`.`Address`, `c0`.`City`, `c0`.`CompanyName`, `c0`.`ContactName`, `c0`.`ContactTitle`, `c0`.`Country`, `c0`.`Fax`, `c0`.`Phone`, `c0`.`PostalCode`, `c0`.`Region`
+ FROM `Customers` AS `c0`
+ ORDER BY `c0`.`CustomerID`
+ LIMIT 2 OFFSET 2
+ ) AS `t0`
+ ORDER BY `t`.`CustomerID`, `t0`.`CustomerID`
+ LIMIT @__p_1
+) AS `t1`
+LEFT JOIN `Orders` AS `o0` ON `t1`.`CustomerID` = `o0`.`CustomerID`
+ORDER BY `t1`.`CustomerID`, `t1`.`CustomerID0`
+""");
+ }
+
+ public override async Task Repro9735(bool async)
+ {
+ await AssertQuery(
+ async,
+ ss => ss.Set()
+ .Include(b => b.OrderDetails)
+ .OrderBy(b => b.Customer.CustomerID != null)
+ .ThenBy(b => b.Customer != null ? b.Customer.CustomerID : string.Empty)
+ .ThenBy(b => b.EmployeeID) // Needs to be explicitly ordered by EmployeeID as well
+ .Take(2),
+ entryCount: 6);
+
+ AssertSql(
+"""
+@__p_0='2'
+
+SELECT `t`.`OrderID`, `t`.`CustomerID`, `t`.`EmployeeID`, `t`.`OrderDate`, `t`.`CustomerID0`, `o0`.`OrderID`, `o0`.`ProductID`, `o0`.`Discount`, `o0`.`Quantity`, `o0`.`UnitPrice`
+FROM (
+ SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, `c`.`CustomerID` AS `CustomerID0`, `c`.`CustomerID` IS NOT NULL AS `c`, CASE
+ WHEN `c`.`CustomerID` IS NOT NULL THEN `c`.`CustomerID`
+ ELSE ''
+ END AS `c0`
+ FROM `Orders` AS `o`
+ LEFT JOIN `Customers` AS `c` ON `o`.`CustomerID` = `c`.`CustomerID`
+ ORDER BY `c`.`CustomerID` IS NOT NULL, CASE
+ WHEN `c`.`CustomerID` IS NOT NULL THEN `c`.`CustomerID`
+ ELSE ''
+ END, `o`.`EmployeeID`
+ LIMIT @__p_0
+) AS `t`
+LEFT JOIN `Order Details` AS `o0` ON `t`.`OrderID` = `o0`.`OrderID`
+ORDER BY `t`.`c`, `t`.`c0`, `t`.`EmployeeID`, `t`.`OrderID`, `t`.`CustomerID0`, `o0`.`OrderID`
+""");
+ }
+
+ public override async Task Include_collection_single_or_default_no_result(bool async)
+ {
+ await base.Include_collection_single_or_default_no_result(async);
+
+ AssertSql(
+"""
+SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+FROM (
+ 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`.`CustomerID` = 'ALFKI ?'
+ LIMIT 2
+) AS `t`
+LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID`
+ORDER BY `t`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_collection_with_cross_apply_with_filter(bool async)
+ {
+ await base.Include_collection_with_cross_apply_with_filter(async);
+
+ AssertSql(
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `t`.`OrderID`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`
+FROM `Customers` AS `c`
+JOIN LATERAL (
+ SELECT `o`.`OrderID`
+ FROM `Orders` AS `o`
+ WHERE `o`.`CustomerID` = `c`.`CustomerID`
+ ORDER BY `c`.`CustomerID`
+ LIMIT 5
+) AS `t` ON TRUE
+LEFT JOIN `Orders` AS `o0` ON `c`.`CustomerID` = `o0`.`CustomerID`
+WHERE `c`.`CustomerID` LIKE 'F%'
+ORDER BY `c`.`CustomerID`, `t`.`OrderID`
+""");
+ }
+
+ public override async Task Include_collection_with_left_join_clause_with_filter(bool async)
+ {
+ await base.Include_collection_with_left_join_clause_with_filter(async);
+
+ AssertSql(
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `o`.`OrderID`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`
+FROM `Customers` AS `c`
+LEFT JOIN `Orders` AS `o` ON `c`.`CustomerID` = `o`.`CustomerID`
+LEFT JOIN `Orders` AS `o0` ON `c`.`CustomerID` = `o0`.`CustomerID`
+WHERE `c`.`CustomerID` LIKE 'F%'
+ORDER BY `c`.`CustomerID`, `o`.`OrderID`
+""");
+ }
+
+ public override async Task Include_duplicate_collection(bool async)
+ {
+ await base.Include_duplicate_collection(async);
+
+ AssertSql(
+"""
+@__p_0='2'
+
+SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `t0`.`CustomerID`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, `t0`.`Address`, `t0`.`City`, `t0`.`CompanyName`, `t0`.`ContactName`, `t0`.`ContactTitle`, `t0`.`Country`, `t0`.`Fax`, `t0`.`Phone`, `t0`.`PostalCode`, `t0`.`Region`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`
+FROM (
+ 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`
+ ORDER BY `c`.`CustomerID`
+ LIMIT @__p_0
+) AS `t`
+CROSS JOIN (
+ SELECT `c0`.`CustomerID`, `c0`.`Address`, `c0`.`City`, `c0`.`CompanyName`, `c0`.`ContactName`, `c0`.`ContactTitle`, `c0`.`Country`, `c0`.`Fax`, `c0`.`Phone`, `c0`.`PostalCode`, `c0`.`Region`
+ FROM `Customers` AS `c0`
+ ORDER BY `c0`.`CustomerID`
+ LIMIT 2 OFFSET 2
+) AS `t0`
+LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID`
+LEFT JOIN `Orders` AS `o0` ON `t0`.`CustomerID` = `o0`.`CustomerID`
+ORDER BY `t`.`CustomerID`, `t0`.`CustomerID`, `o`.`OrderID`
+""");
+ }
+
+ public override async Task Include_collection(bool async)
+ {
+ await base.Include_collection(async);
+
+ AssertSql(
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+FROM `Customers` AS `c`
+LEFT JOIN `Orders` AS `o` ON `c`.`CustomerID` = `o`.`CustomerID`
+WHERE `c`.`CustomerID` LIKE 'F%'
+ORDER BY `c`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_collection_then_include_collection_then_include_reference(bool async)
+ {
+ await base.Include_collection_then_include_collection_then_include_reference(async);
+
+ AssertSql(
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `t0`.`OrderID`, `t0`.`CustomerID`, `t0`.`EmployeeID`, `t0`.`OrderDate`, `t0`.`OrderID0`, `t0`.`ProductID`, `t0`.`Discount`, `t0`.`Quantity`, `t0`.`UnitPrice`, `t0`.`ProductID0`, `t0`.`Discontinued`, `t0`.`ProductName`, `t0`.`SupplierID`, `t0`.`UnitPrice0`, `t0`.`UnitsInStock`
+FROM `Customers` AS `c`
+LEFT JOIN (
+ SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, `t`.`OrderID` AS `OrderID0`, `t`.`ProductID`, `t`.`Discount`, `t`.`Quantity`, `t`.`UnitPrice`, `t`.`ProductID0`, `t`.`Discontinued`, `t`.`ProductName`, `t`.`SupplierID`, `t`.`UnitPrice0`, `t`.`UnitsInStock`
+ FROM `Orders` AS `o`
+ LEFT JOIN (
+ SELECT `o0`.`OrderID`, `o0`.`ProductID`, `o0`.`Discount`, `o0`.`Quantity`, `o0`.`UnitPrice`, `p`.`ProductID` AS `ProductID0`, `p`.`Discontinued`, `p`.`ProductName`, `p`.`SupplierID`, `p`.`UnitPrice` AS `UnitPrice0`, `p`.`UnitsInStock`
+ FROM `Order Details` AS `o0`
+ INNER JOIN `Products` AS `p` ON `o0`.`ProductID` = `p`.`ProductID`
+ ) AS `t` ON `o`.`OrderID` = `t`.`OrderID`
+) AS `t0` ON `c`.`CustomerID` = `t0`.`CustomerID`
+WHERE `c`.`CustomerID` LIKE 'F%'
+ORDER BY `c`.`CustomerID`, `t0`.`OrderID`, `t0`.`OrderID0`, `t0`.`ProductID`
+""");
+ }
+
+ public override async Task Include_reference_GroupBy_Select(bool async)
+ {
+ await base.Include_reference_GroupBy_Select(async);
+
+ AssertSql(
+"""
+SELECT `t0`.`OrderID`, `t0`.`CustomerID`, `t0`.`EmployeeID`, `t0`.`OrderDate`, `t0`.`CustomerID0`, `t0`.`Address`, `t0`.`City`, `t0`.`CompanyName`, `t0`.`ContactName`, `t0`.`ContactTitle`, `t0`.`Country`, `t0`.`Fax`, `t0`.`Phone`, `t0`.`PostalCode`, `t0`.`Region`
+FROM (
+ SELECT `o`.`OrderID`
+ FROM `Orders` AS `o`
+ WHERE `o`.`OrderID` = 10248
+ GROUP BY `o`.`OrderID`
+) AS `t`
+LEFT JOIN (
+ SELECT `t1`.`OrderID`, `t1`.`CustomerID`, `t1`.`EmployeeID`, `t1`.`OrderDate`, `t1`.`CustomerID0`, `t1`.`Address`, `t1`.`City`, `t1`.`CompanyName`, `t1`.`ContactName`, `t1`.`ContactTitle`, `t1`.`Country`, `t1`.`Fax`, `t1`.`Phone`, `t1`.`PostalCode`, `t1`.`Region`
+ FROM (
+ SELECT `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`, `c`.`CustomerID` AS `CustomerID0`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, ROW_NUMBER() OVER(PARTITION BY `o0`.`OrderID` ORDER BY `o0`.`OrderID`) AS `row`
+ FROM `Orders` AS `o0`
+ LEFT JOIN `Customers` AS `c` ON `o0`.`CustomerID` = `c`.`CustomerID`
+ WHERE `o0`.`OrderID` = 10248
+ ) AS `t1`
+ WHERE `t1`.`row` <= 1
+) AS `t0` ON `t`.`OrderID` = `t0`.`OrderID`
+""");
+ }
+
+ public override async Task Include_multiple_references_multi_level_reverse(bool async)
+ {
+ await base.Include_multiple_references_multi_level_reverse(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`, `p`.`ProductID`, `p`.`Discontinued`, `p`.`ProductName`, `p`.`SupplierID`, `p`.`UnitPrice`, `p`.`UnitsInStock`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`, `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+FROM `Order Details` AS `o`
+INNER JOIN `Products` AS `p` ON `o`.`ProductID` = `p`.`ProductID`
+INNER JOIN `Orders` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+LEFT JOIN `Customers` AS `c` ON `o0`.`CustomerID` = `c`.`CustomerID`
+WHERE (`o`.`OrderID` % 23) = 13
+""");
+ }
+
+ public override async Task Include_collection_with_join_clause_with_filter(bool async)
+ {
+ await base.Include_collection_with_join_clause_with_filter(async);
+
+ AssertSql(
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `o`.`OrderID`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`
+FROM `Customers` AS `c`
+INNER JOIN `Orders` AS `o` ON `c`.`CustomerID` = `o`.`CustomerID`
+LEFT JOIN `Orders` AS `o0` ON `c`.`CustomerID` = `o0`.`CustomerID`
+WHERE `c`.`CustomerID` LIKE 'F%'
+ORDER BY `c`.`CustomerID`, `o`.`OrderID`
+""");
+ }
+
+ public override async Task Include_collection_OrderBy_list_does_not_contains(bool async)
+ {
+ await base.Include_collection_OrderBy_list_does_not_contains(async);
+
+ AssertSql(
+"""
+@__p_1='1'
+
+SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+FROM (
+ SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `c`.`CustomerID` <> 'ALFKI' AS `c`
+ FROM `Customers` AS `c`
+ WHERE `c`.`CustomerID` LIKE 'A%'
+ ORDER BY `c`.`CustomerID` <> 'ALFKI'
+ LIMIT 18446744073709551610 OFFSET @__p_1
+) AS `t`
+LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID`
+ORDER BY `t`.`c`, `t`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_reference_dependent_already_tracked(bool async)
+ {
+ await base.Include_reference_dependent_already_tracked(async);
+
+ AssertSql(
+"""
+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`.`CustomerID` = 'ALFKI'
+LIMIT 2
+""",
+ //
+ """
+SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+FROM `Orders` AS `o`
+LEFT JOIN `Customers` AS `c` ON `o`.`CustomerID` = `c`.`CustomerID`
+WHERE `o`.`CustomerID` = 'ALFKI'
+""");
+ }
+
+ public override async Task Include_reference_with_filter(bool async)
+ {
+ await base.Include_reference_with_filter(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+FROM `Orders` AS `o`
+LEFT JOIN `Customers` AS `c` ON `o`.`CustomerID` = `c`.`CustomerID`
+WHERE `o`.`CustomerID` = 'ALFKI'
+""");
+ }
+
+ public override async Task Include_duplicate_reference(bool async)
+ {
+ await base.Include_duplicate_reference(async);
+
+ AssertSql(
+"""
+@__p_0='2'
+
+SELECT `t`.`OrderID`, `t`.`CustomerID`, `t`.`EmployeeID`, `t`.`OrderDate`, `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `t0`.`OrderID`, `t0`.`CustomerID`, `t0`.`EmployeeID`, `t0`.`OrderDate`, `c0`.`CustomerID`, `c0`.`Address`, `c0`.`City`, `c0`.`CompanyName`, `c0`.`ContactName`, `c0`.`ContactTitle`, `c0`.`Country`, `c0`.`Fax`, `c0`.`Phone`, `c0`.`PostalCode`, `c0`.`Region`
+FROM (
+ SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+ FROM `Orders` AS `o`
+ ORDER BY `o`.`CustomerID`, `o`.`OrderID`
+ LIMIT @__p_0
+) AS `t`
+CROSS JOIN (
+ SELECT `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`
+ FROM `Orders` AS `o0`
+ ORDER BY `o0`.`CustomerID`, `o0`.`OrderID`
+ LIMIT 2 OFFSET 2
+) AS `t0`
+LEFT JOIN `Customers` AS `c` ON `t`.`CustomerID` = `c`.`CustomerID`
+LEFT JOIN `Customers` AS `c0` ON `t0`.`CustomerID` = `c0`.`CustomerID`
+ORDER BY `t`.`CustomerID`, `t`.`OrderID`
+""");
+ }
+
+ public override async Task Include_with_complex_projection(bool async)
+ {
+ await base.Include_with_complex_projection(async);
+
+ AssertSql(
+"""
+SELECT `c`.`CustomerID` AS `Id`
+FROM `Orders` AS `o`
+LEFT JOIN `Customers` AS `c` ON `o`.`CustomerID` = `c`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_collection_order_by_non_key_with_skip(bool async)
+ {
+ await base.Include_collection_order_by_non_key_with_skip(async);
+
+ AssertSql(
+"""
+@__p_0='2'
+
+SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+FROM (
+ 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`.`CustomerID` LIKE 'F%'
+ ORDER BY `c`.`ContactTitle`
+ LIMIT 18446744073709551610 OFFSET @__p_0
+) AS `t`
+LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID`
+ORDER BY `t`.`ContactTitle`, `t`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_collection_on_join_clause_with_order_by_and_filter(bool async)
+ {
+ await base.Include_collection_on_join_clause_with_order_by_and_filter(async);
+
+ AssertSql(
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `o`.`OrderID`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`
+FROM `Customers` AS `c`
+INNER JOIN `Orders` AS `o` ON `c`.`CustomerID` = `o`.`CustomerID`
+LEFT JOIN `Orders` AS `o0` ON `c`.`CustomerID` = `o0`.`CustomerID`
+WHERE `c`.`CustomerID` = 'ALFKI'
+ORDER BY `c`.`City`, `c`.`CustomerID`, `o`.`OrderID`
+""");
+ }
+
+ public override async Task Multi_level_includes_are_applied_with_take(bool async)
+ {
+ await base.Multi_level_includes_are_applied_with_take(async);
+
+ AssertSql(
+"""
+@__p_0='1'
+
+SELECT `t0`.`CustomerID`, `t1`.`OrderID`, `t1`.`CustomerID`, `t1`.`EmployeeID`, `t1`.`OrderDate`, `t1`.`OrderID0`, `t1`.`ProductID`, `t1`.`Discount`, `t1`.`Quantity`, `t1`.`UnitPrice`
+FROM (
+ SELECT `t`.`CustomerID`
+ FROM (
+ SELECT `c`.`CustomerID`
+ FROM `Customers` AS `c`
+ WHERE `c`.`CustomerID` LIKE 'A%'
+ ORDER BY `c`.`CustomerID`
+ LIMIT @__p_0
+ ) AS `t`
+ ORDER BY `t`.`CustomerID`
+ LIMIT 1
+) AS `t0`
+LEFT JOIN (
+ SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, `o0`.`OrderID` AS `OrderID0`, `o0`.`ProductID`, `o0`.`Discount`, `o0`.`Quantity`, `o0`.`UnitPrice`
+ FROM `Orders` AS `o`
+ LEFT JOIN `Order Details` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+) AS `t1` ON `t0`.`CustomerID` = `t1`.`CustomerID`
+ORDER BY `t0`.`CustomerID`, `t1`.`OrderID`, `t1`.`OrderID0`
+""");
+ }
+
+ public override async Task Include_multiple_references_then_include_collection_multi_level_reverse(bool async)
+ {
+ await base.Include_multiple_references_then_include_collection_multi_level_reverse(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`, `p`.`ProductID`, `p`.`Discontinued`, `p`.`ProductName`, `p`.`SupplierID`, `p`.`UnitPrice`, `p`.`UnitsInStock`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`, `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `o1`.`OrderID`, `o1`.`CustomerID`, `o1`.`EmployeeID`, `o1`.`OrderDate`
+FROM `Order Details` AS `o`
+INNER JOIN `Products` AS `p` ON `o`.`ProductID` = `p`.`ProductID`
+INNER JOIN `Orders` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+LEFT JOIN `Customers` AS `c` ON `o0`.`CustomerID` = `c`.`CustomerID`
+LEFT JOIN `Orders` AS `o1` ON `c`.`CustomerID` = `o1`.`CustomerID`
+WHERE (`o`.`OrderID` % 23) = 13
+ORDER BY `o`.`OrderID`, `o`.`ProductID`, `p`.`ProductID`, `o0`.`OrderID`, `c`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_collection_then_reference(bool async)
+ {
+ await base.Include_collection_then_reference(async);
+
+ AssertSql(
+"""
+SELECT `p`.`ProductID`, `p`.`Discontinued`, `p`.`ProductName`, `p`.`SupplierID`, `p`.`UnitPrice`, `p`.`UnitsInStock`, `t`.`OrderID`, `t`.`ProductID`, `t`.`Discount`, `t`.`Quantity`, `t`.`UnitPrice`, `t`.`OrderID0`, `t`.`CustomerID`, `t`.`EmployeeID`, `t`.`OrderDate`
+FROM `Products` AS `p`
+LEFT JOIN (
+ SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`, `o0`.`OrderID` AS `OrderID0`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`
+ FROM `Order Details` AS `o`
+ INNER JOIN `Orders` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+) AS `t` ON `p`.`ProductID` = `t`.`ProductID`
+WHERE (`p`.`ProductID` % 17) = 5
+ORDER BY `p`.`ProductID`, `t`.`OrderID`, `t`.`ProductID`
+""");
+ }
+
+ public override async Task Include_collection_order_by_key(bool async)
+ {
+ await base.Include_collection_order_by_key(async);
+
+ AssertSql(
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+FROM `Customers` AS `c`
+LEFT JOIN `Orders` AS `o` ON `c`.`CustomerID` = `o`.`CustomerID`
+WHERE `c`.`CustomerID` LIKE 'F%'
+ORDER BY `c`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_collection_with_outer_apply_with_filter(bool async)
+ {
+ await base.Include_collection_with_outer_apply_with_filter(async);
+
+ AssertSql(
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `t`.`OrderID`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`
+FROM `Customers` AS `c`
+LEFT JOIN LATERAL (
+ SELECT `o`.`OrderID`
+ FROM `Orders` AS `o`
+ WHERE `o`.`CustomerID` = `c`.`CustomerID`
+ ORDER BY `c`.`CustomerID`
+ LIMIT 5
+) AS `t` ON TRUE
+LEFT JOIN `Orders` AS `o0` ON `c`.`CustomerID` = `o0`.`CustomerID`
+WHERE `c`.`CustomerID` LIKE 'F%'
+ORDER BY `c`.`CustomerID`, `t`.`OrderID`
+""");
+ }
+
+ public override async Task Include_collection_on_additional_from_clause2(bool async)
+ {
+ await base.Include_collection_on_additional_from_clause2(async);
+
+ AssertSql(
+"""
+@__p_0='5'
+
+SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`
+FROM (
+ 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`
+ ORDER BY `c`.`CustomerID`
+ LIMIT @__p_0
+) AS `t`
+CROSS JOIN `Customers` AS `c0`
+ORDER BY `t`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_collection_dependent_already_tracked(bool async)
+ {
+ await base.Include_collection_dependent_already_tracked(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+FROM `Orders` AS `o`
+WHERE `o`.`CustomerID` = 'ALFKI'
+""",
+ //
+ """
+SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+FROM (
+ 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`.`CustomerID` = 'ALFKI'
+ LIMIT 2
+) AS `t`
+LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID`
+ORDER BY `t`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_with_complex_projection_does_not_change_ordering_of_projection(bool async)
+ {
+ await base.Include_with_complex_projection_does_not_change_ordering_of_projection(async);
+
+ AssertSql(
+"""
+SELECT `c`.`CustomerID` AS `Id`, (
+ SELECT COUNT(*)
+ FROM `Orders` AS `o0`
+ WHERE `c`.`CustomerID` = `o0`.`CustomerID`) AS `TotalOrders`
+FROM `Customers` AS `c`
+WHERE (`c`.`ContactTitle` = 'Owner') AND ((
+ SELECT COUNT(*)
+ FROM `Orders` AS `o`
+ WHERE `c`.`CustomerID` = `o`.`CustomerID`) > 2)
+ORDER BY `c`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_multi_level_collection_and_then_include_reference_predicate(bool async)
+ {
+ await base.Include_multi_level_collection_and_then_include_reference_predicate(async);
+
+ AssertSql(
+"""
+SELECT `t`.`OrderID`, `t`.`CustomerID`, `t`.`EmployeeID`, `t`.`OrderDate`, `t0`.`OrderID`, `t0`.`ProductID`, `t0`.`Discount`, `t0`.`Quantity`, `t0`.`UnitPrice`, `t0`.`ProductID0`, `t0`.`Discontinued`, `t0`.`ProductName`, `t0`.`SupplierID`, `t0`.`UnitPrice0`, `t0`.`UnitsInStock`
+FROM (
+ SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+ FROM `Orders` AS `o`
+ WHERE `o`.`OrderID` = 10248
+ LIMIT 2
+) AS `t`
+LEFT JOIN (
+ SELECT `o0`.`OrderID`, `o0`.`ProductID`, `o0`.`Discount`, `o0`.`Quantity`, `o0`.`UnitPrice`, `p`.`ProductID` AS `ProductID0`, `p`.`Discontinued`, `p`.`ProductName`, `p`.`SupplierID`, `p`.`UnitPrice` AS `UnitPrice0`, `p`.`UnitsInStock`
+ FROM `Order Details` AS `o0`
+ INNER JOIN `Products` AS `p` ON `o0`.`ProductID` = `p`.`ProductID`
+) AS `t0` ON `t`.`OrderID` = `t0`.`OrderID`
+ORDER BY `t`.`OrderID`, `t0`.`OrderID`, `t0`.`ProductID`
+""");
+ }
+
+ public override async Task Multi_level_includes_are_applied_with_skip_take(bool async)
+ {
+ await base.Multi_level_includes_are_applied_with_skip_take(async);
+
+ AssertSql(
+"""
+@__p_0='1'
+
+SELECT `t0`.`CustomerID`, `t1`.`OrderID`, `t1`.`CustomerID`, `t1`.`EmployeeID`, `t1`.`OrderDate`, `t1`.`OrderID0`, `t1`.`ProductID`, `t1`.`Discount`, `t1`.`Quantity`, `t1`.`UnitPrice`
+FROM (
+ SELECT `t`.`CustomerID`
+ FROM (
+ SELECT `c`.`CustomerID`
+ FROM `Customers` AS `c`
+ WHERE `c`.`CustomerID` LIKE 'A%'
+ ORDER BY `c`.`CustomerID`
+ LIMIT @__p_0 OFFSET @__p_0
+ ) AS `t`
+ ORDER BY `t`.`CustomerID`
+ LIMIT 1
+) AS `t0`
+LEFT JOIN (
+ SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, `o0`.`OrderID` AS `OrderID0`, `o0`.`ProductID`, `o0`.`Discount`, `o0`.`Quantity`, `o0`.`UnitPrice`
+ FROM `Orders` AS `o`
+ LEFT JOIN `Order Details` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+) AS `t1` ON `t0`.`CustomerID` = `t1`.`CustomerID`
+ORDER BY `t0`.`CustomerID`, `t1`.`OrderID`, `t1`.`OrderID0`
+""");
+ }
+
+ public override async Task Include_collection_OrderBy_empty_list_contains(bool async)
+ {
+ await base.Include_collection_OrderBy_empty_list_contains(async);
+
+ AssertSql(
+"""
+@__p_1='1'
+
+SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+FROM (
+ 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 (SELECT 1)
+ LIMIT 18446744073709551610 OFFSET @__p_1
+) AS `t`
+LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID`
+ORDER BY `t`.`c`, `t`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_references_and_collection_multi_level(bool async)
+ {
+ await base.Include_references_and_collection_multi_level(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`, `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `o1`.`OrderID`, `o1`.`CustomerID`, `o1`.`EmployeeID`, `o1`.`OrderDate`
+FROM `Order Details` AS `o`
+INNER JOIN `Orders` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+LEFT JOIN `Customers` AS `c` ON `o0`.`CustomerID` = `c`.`CustomerID`
+LEFT JOIN `Orders` AS `o1` ON `c`.`CustomerID` = `o1`.`CustomerID`
+WHERE ((`o`.`OrderID` % 23) = 13) AND (`o`.`UnitPrice` < 10.0)
+ORDER BY `o`.`OrderID`, `o`.`ProductID`, `o0`.`OrderID`, `c`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_collection_force_alias_uniquefication(bool async)
+ {
+ await base.Include_collection_force_alias_uniquefication(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, `o0`.`OrderID`, `o0`.`ProductID`, `o0`.`Discount`, `o0`.`Quantity`, `o0`.`UnitPrice`
+FROM `Orders` AS `o`
+LEFT JOIN `Order Details` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+WHERE `o`.`CustomerID` = 'ALFKI'
+ORDER BY `o`.`OrderID`, `o0`.`OrderID`
+""");
+ }
+
+ public override async Task Include_collection_with_outer_apply_with_filter_non_equality(bool async)
+ {
+ await base.Include_collection_with_outer_apply_with_filter_non_equality(async);
+
+ AssertSql(
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `t`.`OrderID`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`
+FROM `Customers` AS `c`
+LEFT JOIN LATERAL (
+ SELECT `o`.`OrderID`
+ FROM `Orders` AS `o`
+ WHERE (`o`.`CustomerID` <> `c`.`CustomerID`) OR `o`.`CustomerID` IS NULL
+ ORDER BY `c`.`CustomerID`
+ LIMIT 5
+) AS `t` ON TRUE
+LEFT JOIN `Orders` AS `o0` ON `c`.`CustomerID` = `o0`.`CustomerID`
+WHERE `c`.`CustomerID` LIKE 'F%'
+ORDER BY `c`.`CustomerID`, `t`.`OrderID`
+""");
+ }
+
+ public override async Task Include_in_let_followed_by_FirstOrDefault(bool async)
+ {
+ await base.Include_in_let_followed_by_FirstOrDefault(async);
+
+ AssertSql(
+"""
+SELECT `c`.`CustomerID`, `t0`.`OrderID`, `t0`.`CustomerID`, `t0`.`EmployeeID`, `t0`.`OrderDate`, `o0`.`OrderID`, `o0`.`ProductID`, `o0`.`Discount`, `o0`.`Quantity`, `o0`.`UnitPrice`
+FROM `Customers` AS `c`
+LEFT JOIN (
+ SELECT `t`.`OrderID`, `t`.`CustomerID`, `t`.`EmployeeID`, `t`.`OrderDate`
+ FROM (
+ SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, ROW_NUMBER() OVER(PARTITION BY `o`.`CustomerID` ORDER BY `o`.`OrderDate`) AS `row`
+ FROM `Orders` AS `o`
+ ) AS `t`
+ WHERE `t`.`row` <= 1
+) AS `t0` ON `c`.`CustomerID` = `t0`.`CustomerID`
+LEFT JOIN `Order Details` AS `o0` ON `t0`.`OrderID` = `o0`.`OrderID`
+WHERE `c`.`CustomerID` LIKE 'F%'
+ORDER BY `c`.`CustomerID`, `t0`.`OrderID`, `o0`.`OrderID`
+""");
+ }
+
+ public override async Task Include_references_multi_level(bool async)
+ {
+ await base.Include_references_multi_level(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`, `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+FROM `Order Details` AS `o`
+INNER JOIN `Orders` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+LEFT JOIN `Customers` AS `c` ON `o0`.`CustomerID` = `c`.`CustomerID`
+WHERE (`o`.`OrderID` % 23) = 13
+""");
+ }
+
+ public override async Task Include_collection_then_include_collection(bool async)
+ {
+ await base.Include_collection_then_include_collection(async);
+
+ AssertSql(
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `t`.`OrderID`, `t`.`CustomerID`, `t`.`EmployeeID`, `t`.`OrderDate`, `t`.`OrderID0`, `t`.`ProductID`, `t`.`Discount`, `t`.`Quantity`, `t`.`UnitPrice`
+FROM `Customers` AS `c`
+LEFT JOIN (
+ SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, `o0`.`OrderID` AS `OrderID0`, `o0`.`ProductID`, `o0`.`Discount`, `o0`.`Quantity`, `o0`.`UnitPrice`
+ FROM `Orders` AS `o`
+ LEFT JOIN `Order Details` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+) AS `t` ON `c`.`CustomerID` = `t`.`CustomerID`
+WHERE `c`.`CustomerID` LIKE 'F%'
+ORDER BY `c`.`CustomerID`, `t`.`OrderID`, `t`.`OrderID0`
+""");
+ }
+
+ public override async Task Include_collection_with_multiple_conditional_order_by(bool async)
+ {
+ // The order of `Orders` can be different, because it is not explicitly sorted.
+ // This is the case on MariaDB.
+ await AssertQuery(
+ async,
+ ss => ss.Set()
+ .Include(c => c.OrderDetails)
+ .OrderBy(o => o.OrderID > 0)
+ .ThenBy(o => o.Customer != null ? o.Customer.City : string.Empty)
+ .ThenBy(o => o.OrderID) // Needs to be explicitly ordered by EmployeeID as well
+ .Take(5),
+ elementAsserter: (e, a) => AssertInclude(e, a, new ExpectedInclude(o => o.OrderDetails)),
+ entryCount: 14);
+
+ AssertSql(
+"""
+@__p_0='5'
+
+SELECT `t`.`OrderID`, `t`.`CustomerID`, `t`.`EmployeeID`, `t`.`OrderDate`, `t`.`CustomerID0`, `o0`.`OrderID`, `o0`.`ProductID`, `o0`.`Discount`, `o0`.`Quantity`, `o0`.`UnitPrice`
+FROM (
+ SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, `c`.`CustomerID` AS `CustomerID0`, `o`.`OrderID` > 0 AS `c`, CASE
+ WHEN `c`.`CustomerID` IS NOT NULL THEN `c`.`City`
+ ELSE ''
+ END AS `c0`
+ FROM `Orders` AS `o`
+ LEFT JOIN `Customers` AS `c` ON `o`.`CustomerID` = `c`.`CustomerID`
+ ORDER BY `o`.`OrderID` > 0, CASE
+ WHEN `c`.`CustomerID` IS NOT NULL THEN `c`.`City`
+ ELSE ''
+ END, `o`.`OrderID`
+ LIMIT @__p_0
+) AS `t`
+LEFT JOIN `Order Details` AS `o0` ON `t`.`OrderID` = `o0`.`OrderID`
+ORDER BY `t`.`c`, `t`.`c0`, `t`.`OrderID`, `t`.`CustomerID0`, `o0`.`OrderID`
+""");
+ }
+
+ public override async Task Include_reference_when_entity_in_projection(bool async)
+ {
+ await base.Include_reference_when_entity_in_projection(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+FROM `Orders` AS `o`
+LEFT JOIN `Customers` AS `c` ON `o`.`CustomerID` = `c`.`CustomerID`
+WHERE `o`.`CustomerID` IS NOT NULL AND (`o`.`CustomerID` LIKE 'F%')
+""");
+ }
+
+ public override async Task Include_reference_single_or_default_when_no_result(bool async)
+ {
+ await base.Include_reference_single_or_default_when_no_result(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+FROM `Orders` AS `o`
+LEFT JOIN `Customers` AS `c` ON `o`.`CustomerID` = `c`.`CustomerID`
+WHERE `o`.`OrderID` = -1
+LIMIT 2
+""");
+ }
+
+ public override async Task Include_reference_alias_generation(bool async)
+ {
+ await base.Include_reference_alias_generation(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`
+FROM `Order Details` AS `o`
+INNER JOIN `Orders` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+WHERE (`o`.`OrderID` % 23) = 13
+""");
+ }
+
+ public override async Task Include_with_cycle_does_not_throw_when_AsNoTrackingWithIdentityResolution(bool async)
+ {
+ await base.Include_with_cycle_does_not_throw_when_AsNoTrackingWithIdentityResolution(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`
+FROM `Orders` AS `o`
+LEFT JOIN `Customers` AS `c` ON `o`.`CustomerID` = `c`.`CustomerID`
+LEFT JOIN `Orders` AS `o0` ON `c`.`CustomerID` = `o0`.`CustomerID`
+WHERE `o`.`OrderID` < 10800
+ORDER BY `o`.`OrderID`, `c`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_references_then_include_collection_multi_level(bool async)
+ {
+ await base.Include_references_then_include_collection_multi_level(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`, `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `o1`.`OrderID`, `o1`.`CustomerID`, `o1`.`EmployeeID`, `o1`.`OrderDate`
+FROM `Order Details` AS `o`
+INNER JOIN `Orders` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+LEFT JOIN `Customers` AS `c` ON `o0`.`CustomerID` = `c`.`CustomerID`
+LEFT JOIN `Orders` AS `o1` ON `c`.`CustomerID` = `o1`.`CustomerID`
+WHERE ((`o`.`ProductID` % 23) = 17) AND (`o`.`Quantity` < 10)
+ORDER BY `o`.`OrderID`, `o`.`ProductID`, `o0`.`OrderID`, `c`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_reference_Join_GroupBy_Select(bool async)
+ {
+ await base.Include_reference_Join_GroupBy_Select(async);
+
+ AssertSql(
+"""
+SELECT `t0`.`OrderID`, `t0`.`CustomerID`, `t0`.`EmployeeID`, `t0`.`OrderDate`, `t0`.`CustomerID0`, `t0`.`Address`, `t0`.`City`, `t0`.`CompanyName`, `t0`.`ContactName`, `t0`.`ContactTitle`, `t0`.`Country`, `t0`.`Fax`, `t0`.`Phone`, `t0`.`PostalCode`, `t0`.`Region`
+FROM (
+ SELECT `o`.`OrderID`
+ FROM `Orders` AS `o`
+ INNER JOIN `Order Details` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+ WHERE `o`.`OrderID` = 10248
+ GROUP BY `o`.`OrderID`
+) AS `t`
+LEFT JOIN (
+ SELECT `t1`.`OrderID`, `t1`.`CustomerID`, `t1`.`EmployeeID`, `t1`.`OrderDate`, `t1`.`CustomerID0`, `t1`.`Address`, `t1`.`City`, `t1`.`CompanyName`, `t1`.`ContactName`, `t1`.`ContactTitle`, `t1`.`Country`, `t1`.`Fax`, `t1`.`Phone`, `t1`.`PostalCode`, `t1`.`Region`
+ FROM (
+ SELECT `o1`.`OrderID`, `o1`.`CustomerID`, `o1`.`EmployeeID`, `o1`.`OrderDate`, `c`.`CustomerID` AS `CustomerID0`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, ROW_NUMBER() OVER(PARTITION BY `o1`.`OrderID` ORDER BY `o1`.`OrderID`) AS `row`
+ FROM `Orders` AS `o1`
+ INNER JOIN `Order Details` AS `o2` ON `o1`.`OrderID` = `o2`.`OrderID`
+ LEFT JOIN `Customers` AS `c` ON `o1`.`CustomerID` = `c`.`CustomerID`
+ WHERE `o1`.`OrderID` = 10248
+ ) AS `t1`
+ WHERE `t1`.`row` <= 1
+) AS `t0` ON `t`.`OrderID` = `t0`.`OrderID`
+""");
+ }
+
+ public override async Task Include_collection_when_projection(bool async)
+ {
+ await base.Include_collection_when_projection(async);
+
+ AssertSql(
+"""
+SELECT `c`.`CustomerID`
+FROM `Customers` AS `c`
+""");
+ }
+
+ public override async Task Include_reference_SelectMany_GroupBy_Select(bool async)
+ {
+ await base.Include_reference_SelectMany_GroupBy_Select(async);
+
+ AssertSql(
+"""
+SELECT `t0`.`OrderID`, `t0`.`CustomerID`, `t0`.`EmployeeID`, `t0`.`OrderDate`, `t0`.`CustomerID0`, `t0`.`Address`, `t0`.`City`, `t0`.`CompanyName`, `t0`.`ContactName`, `t0`.`ContactTitle`, `t0`.`Country`, `t0`.`Fax`, `t0`.`Phone`, `t0`.`PostalCode`, `t0`.`Region`
+FROM (
+ SELECT `o`.`OrderID`
+ FROM `Orders` AS `o`
+ CROSS JOIN `Order Details` AS `o0`
+ WHERE `o`.`OrderID` = 10248
+ GROUP BY `o`.`OrderID`
+) AS `t`
+LEFT JOIN (
+ SELECT `t1`.`OrderID`, `t1`.`CustomerID`, `t1`.`EmployeeID`, `t1`.`OrderDate`, `t1`.`CustomerID0`, `t1`.`Address`, `t1`.`City`, `t1`.`CompanyName`, `t1`.`ContactName`, `t1`.`ContactTitle`, `t1`.`Country`, `t1`.`Fax`, `t1`.`Phone`, `t1`.`PostalCode`, `t1`.`Region`
+ FROM (
+ SELECT `o1`.`OrderID`, `o1`.`CustomerID`, `o1`.`EmployeeID`, `o1`.`OrderDate`, `c`.`CustomerID` AS `CustomerID0`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, ROW_NUMBER() OVER(PARTITION BY `o1`.`OrderID` ORDER BY `o1`.`OrderID`) AS `row`
+ FROM `Orders` AS `o1`
+ CROSS JOIN `Order Details` AS `o2`
+ LEFT JOIN `Customers` AS `c` ON `o1`.`CustomerID` = `c`.`CustomerID`
+ WHERE `o1`.`OrderID` = 10248
+ ) AS `t1`
+ WHERE `t1`.`row` <= 1
+) AS `t0` ON `t`.`OrderID` = `t0`.`OrderID`
+""");
+ }
+
+ public override async Task Include_multiple_references_then_include_collection_multi_level(bool async)
+ {
+ await base.Include_multiple_references_then_include_collection_multi_level(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`, `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `p`.`ProductID`, `o1`.`OrderID`, `o1`.`CustomerID`, `o1`.`EmployeeID`, `o1`.`OrderDate`, `p`.`Discontinued`, `p`.`ProductName`, `p`.`SupplierID`, `p`.`UnitPrice`, `p`.`UnitsInStock`
+FROM `Order Details` AS `o`
+INNER JOIN `Orders` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+LEFT JOIN `Customers` AS `c` ON `o0`.`CustomerID` = `c`.`CustomerID`
+INNER JOIN `Products` AS `p` ON `o`.`ProductID` = `p`.`ProductID`
+LEFT JOIN `Orders` AS `o1` ON `c`.`CustomerID` = `o1`.`CustomerID`
+WHERE (`o`.`OrderID` % 23) = 13
+ORDER BY `o`.`OrderID`, `o`.`ProductID`, `o0`.`OrderID`, `c`.`CustomerID`, `p`.`ProductID`
+""");
+ }
+
+ public override async Task Outer_idenfier_correctly_determined_when_doing_include_on_right_side_of_left_join(bool async)
+ {
+ await base.Outer_idenfier_correctly_determined_when_doing_include_on_right_side_of_left_join(async);
+
+ AssertSql(
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, `o0`.`OrderID`, `o0`.`ProductID`, `o0`.`Discount`, `o0`.`Quantity`, `o0`.`UnitPrice`
+FROM `Customers` AS `c`
+LEFT JOIN `Orders` AS `o` ON `c`.`CustomerID` = `o`.`CustomerID`
+LEFT JOIN `Order Details` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+WHERE `c`.`City` = 'Seattle'
+ORDER BY `c`.`CustomerID`, `o`.`OrderID`, `o0`.`OrderID`
+""");
+ }
+
+ public override async Task SelectMany_Include_reference_GroupBy_Select(bool async)
+ {
+ await base.SelectMany_Include_reference_GroupBy_Select(async);
+
+ AssertSql(
+"""
+SELECT `t0`.`OrderID`, `t0`.`CustomerID`, `t0`.`EmployeeID`, `t0`.`OrderDate`, `t0`.`CustomerID0`, `t0`.`Address`, `t0`.`City`, `t0`.`CompanyName`, `t0`.`ContactName`, `t0`.`ContactTitle`, `t0`.`Country`, `t0`.`Fax`, `t0`.`Phone`, `t0`.`PostalCode`, `t0`.`Region`
+FROM (
+ SELECT `o0`.`OrderID`
+ FROM `Order Details` AS `o`
+ CROSS JOIN `Orders` AS `o0`
+ WHERE `o`.`OrderID` = 10248
+ GROUP BY `o0`.`OrderID`
+) AS `t`
+LEFT JOIN (
+ SELECT `t1`.`OrderID`, `t1`.`CustomerID`, `t1`.`EmployeeID`, `t1`.`OrderDate`, `t1`.`CustomerID0`, `t1`.`Address`, `t1`.`City`, `t1`.`CompanyName`, `t1`.`ContactName`, `t1`.`ContactTitle`, `t1`.`Country`, `t1`.`Fax`, `t1`.`Phone`, `t1`.`PostalCode`, `t1`.`Region`
+ FROM (
+ SELECT `o2`.`OrderID`, `o2`.`CustomerID`, `o2`.`EmployeeID`, `o2`.`OrderDate`, `c`.`CustomerID` AS `CustomerID0`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, ROW_NUMBER() OVER(PARTITION BY `o2`.`OrderID` ORDER BY `o2`.`OrderID`) AS `row`
+ FROM `Order Details` AS `o1`
+ CROSS JOIN `Orders` AS `o2`
+ LEFT JOIN `Customers` AS `c` ON `o2`.`CustomerID` = `c`.`CustomerID`
+ WHERE `o1`.`OrderID` = 10248
+ ) AS `t1`
+ WHERE `t1`.`row` <= 1
+) AS `t0` ON `t`.`OrderID` = `t0`.`OrderID`
+""");
+ }
+
+ public override async Task Include_collection_SelectMany_GroupBy_Select(bool async)
+ {
+ await base.Include_collection_SelectMany_GroupBy_Select(async);
+
+ AssertSql(
+"""
+SELECT `t0`.`OrderID`, `t0`.`CustomerID`, `t0`.`EmployeeID`, `t0`.`OrderDate`, `t`.`OrderID`, `t0`.`OrderID0`, `t0`.`ProductID`, `o3`.`OrderID`, `o3`.`ProductID`, `o3`.`Discount`, `o3`.`Quantity`, `o3`.`UnitPrice`
+FROM (
+ SELECT `o`.`OrderID`
+ FROM `Orders` AS `o`
+ CROSS JOIN `Order Details` AS `o0`
+ WHERE `o`.`OrderID` = 10248
+ GROUP BY `o`.`OrderID`
+) AS `t`
+LEFT JOIN (
+ SELECT `t1`.`OrderID`, `t1`.`CustomerID`, `t1`.`EmployeeID`, `t1`.`OrderDate`, `t1`.`OrderID0`, `t1`.`ProductID`
+ FROM (
+ SELECT `o1`.`OrderID`, `o1`.`CustomerID`, `o1`.`EmployeeID`, `o1`.`OrderDate`, `o2`.`OrderID` AS `OrderID0`, `o2`.`ProductID`, ROW_NUMBER() OVER(PARTITION BY `o1`.`OrderID` ORDER BY `o1`.`OrderID`) AS `row`
+ FROM `Orders` AS `o1`
+ CROSS JOIN `Order Details` AS `o2`
+ WHERE `o1`.`OrderID` = 10248
+ ) AS `t1`
+ WHERE `t1`.`row` <= 1
+) AS `t0` ON `t`.`OrderID` = `t0`.`OrderID`
+LEFT JOIN `Order Details` AS `o3` ON `t0`.`OrderID` = `o3`.`OrderID`
+ORDER BY `t`.`OrderID`, `t0`.`OrderID`, `t0`.`OrderID0`, `t0`.`ProductID`, `o3`.`OrderID`
+""");
+ }
+
+ public override async Task Include_collection_OrderBy_list_contains(bool async)
+ {
+ await base.Include_collection_OrderBy_list_contains(async);
+
+ AssertSql(
+"""
+@__p_1='1'
+
+SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+FROM (
+ SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `c`.`CustomerID` = 'ALFKI' AS `c`
+ FROM `Customers` AS `c`
+ WHERE `c`.`CustomerID` LIKE 'A%'
+ ORDER BY `c`.`CustomerID` = 'ALFKI'
+ LIMIT 18446744073709551610 OFFSET @__p_1
+) AS `t`
+LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID`
+ORDER BY `t`.`c`, `t`.`CustomerID`
+""");
+ }
+
+ public override async Task Multi_level_includes_are_applied_with_skip(bool async)
+ {
+ await base.Multi_level_includes_are_applied_with_skip(async);
+
+ AssertSql(
+"""
+@__p_0='1'
+
+SELECT `t`.`CustomerID`, `t0`.`OrderID`, `t0`.`CustomerID`, `t0`.`EmployeeID`, `t0`.`OrderDate`, `t0`.`OrderID0`, `t0`.`ProductID`, `t0`.`Discount`, `t0`.`Quantity`, `t0`.`UnitPrice`
+FROM (
+ SELECT `c`.`CustomerID`
+ FROM `Customers` AS `c`
+ WHERE `c`.`CustomerID` LIKE 'A%'
+ ORDER BY `c`.`CustomerID`
+ LIMIT 1 OFFSET @__p_0
+) AS `t`
+LEFT JOIN (
+ SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, `o0`.`OrderID` AS `OrderID0`, `o0`.`ProductID`, `o0`.`Discount`, `o0`.`Quantity`, `o0`.`UnitPrice`
+ FROM `Orders` AS `o`
+ LEFT JOIN `Order Details` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+) AS `t0` ON `t`.`CustomerID` = `t0`.`CustomerID`
+ORDER BY `t`.`CustomerID`, `t0`.`OrderID`, `t0`.`OrderID0`
+""");
+ }
+
+ public override async Task Include_collection_on_additional_from_clause(bool async)
+ {
+ await base.Include_collection_on_additional_from_clause(async);
+
+ AssertSql(
+"""
+@__p_0='5'
+
+SELECT `t0`.`CustomerID`, `t0`.`Address`, `t0`.`City`, `t0`.`CompanyName`, `t0`.`ContactName`, `t0`.`ContactTitle`, `t0`.`Country`, `t0`.`Fax`, `t0`.`Phone`, `t0`.`PostalCode`, `t0`.`Region`, `t`.`CustomerID`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+FROM (
+ SELECT `c`.`CustomerID`
+ FROM `Customers` AS `c`
+ ORDER BY `c`.`CustomerID`
+ LIMIT @__p_0
+) AS `t`
+CROSS JOIN (
+ SELECT `c0`.`CustomerID`, `c0`.`Address`, `c0`.`City`, `c0`.`CompanyName`, `c0`.`ContactName`, `c0`.`ContactTitle`, `c0`.`Country`, `c0`.`Fax`, `c0`.`Phone`, `c0`.`PostalCode`, `c0`.`Region`
+ FROM `Customers` AS `c0`
+ WHERE `c0`.`CustomerID` LIKE 'F%'
+) AS `t0`
+LEFT JOIN `Orders` AS `o` ON `t0`.`CustomerID` = `o`.`CustomerID`
+ORDER BY `t`.`CustomerID`, `t0`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_reference_distinct_is_server_evaluated(bool async)
+ {
+ await base.Include_reference_distinct_is_server_evaluated(async);
+
+ AssertSql(
+"""
+SELECT `t`.`OrderID`, `t`.`CustomerID`, `t`.`EmployeeID`, `t`.`OrderDate`, `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+FROM (
+ SELECT DISTINCT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+ FROM `Orders` AS `o`
+ WHERE `o`.`OrderID` < 10250
+) AS `t`
+LEFT JOIN `Customers` AS `c` ON `t`.`CustomerID` = `c`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_collection_distinct_is_server_evaluated(bool async)
+ {
+ await base.Include_collection_distinct_is_server_evaluated(async);
+
+ AssertSql(
+"""
+SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+FROM (
+ SELECT DISTINCT `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`.`CustomerID` LIKE 'A%'
+) AS `t`
+LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID`
+ORDER BY `t`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_reference_when_projection(bool async)
+ {
+ await base.Include_reference_when_projection(async);
+
+ AssertSql(
+"""
+SELECT `o`.`CustomerID`
+FROM `Orders` AS `o`
+""");
+ }
+
+ public override async Task Include_duplicate_collection_result_operator(bool async)
+ {
+ // The order of `Orders` can be different, because it is not explicitly sorted.
+ // The order of the end result can be different as well.
+ // This is the case on MariaDB.
+ await AssertQuery(
+ async,
+ ss => (from c1 in ss.Set().Include(c => c.Orders).OrderBy(c => c.CustomerID).ThenBy(c => c.Orders.FirstOrDefault() != null ? c.Orders.FirstOrDefault().CustomerID : null).Take(2)
+ from c2 in ss.Set().Include(c => c.Orders).OrderBy(c => c.CustomerID).Skip(2).Take(2)
+ select new { c1, c2 }).OrderBy(t => t.c1.CustomerID).ThenBy(t => t.c2.CustomerID).Take(1),
+ elementSorter: e => (e.c1.CustomerID, e.c2.CustomerID),
+ elementAsserter: (e, a) =>
+ {
+ AssertInclude(e.c1, a.c1, new ExpectedInclude(c => c.Orders));
+ AssertInclude(e.c2, a.c2, new ExpectedInclude(c => c.Orders));
+ },
+ entryCount: 15);
+
+ AssertSql(
+"""
+@__p_0='2'
+@__p_1='1'
+
+SELECT `t1`.`CustomerID`, `t1`.`Address`, `t1`.`City`, `t1`.`CompanyName`, `t1`.`ContactName`, `t1`.`ContactTitle`, `t1`.`Country`, `t1`.`Fax`, `t1`.`Phone`, `t1`.`PostalCode`, `t1`.`Region`, `t1`.`CustomerID0`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`, `t1`.`Address0`, `t1`.`City0`, `t1`.`CompanyName0`, `t1`.`ContactName0`, `t1`.`ContactTitle0`, `t1`.`Country0`, `t1`.`Fax0`, `t1`.`Phone0`, `t1`.`PostalCode0`, `t1`.`Region0`, `o1`.`OrderID`, `o1`.`CustomerID`, `o1`.`EmployeeID`, `o1`.`OrderDate`
+FROM (
+ SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `t0`.`CustomerID` AS `CustomerID0`, `t0`.`Address` AS `Address0`, `t0`.`City` AS `City0`, `t0`.`CompanyName` AS `CompanyName0`, `t0`.`ContactName` AS `ContactName0`, `t0`.`ContactTitle` AS `ContactTitle0`, `t0`.`Country` AS `Country0`, `t0`.`Fax` AS `Fax0`, `t0`.`Phone` AS `Phone0`, `t0`.`PostalCode` AS `PostalCode0`, `t0`.`Region` AS `Region0`
+ FROM (
+ 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`
+ ORDER BY `c`.`CustomerID`, (
+ SELECT `o`.`CustomerID`
+ FROM `Orders` AS `o`
+ WHERE `c`.`CustomerID` = `o`.`CustomerID`
+ LIMIT 1)
+ LIMIT @__p_0
+ ) AS `t`
+ CROSS JOIN (
+ SELECT `c0`.`CustomerID`, `c0`.`Address`, `c0`.`City`, `c0`.`CompanyName`, `c0`.`ContactName`, `c0`.`ContactTitle`, `c0`.`Country`, `c0`.`Fax`, `c0`.`Phone`, `c0`.`PostalCode`, `c0`.`Region`
+ FROM `Customers` AS `c0`
+ ORDER BY `c0`.`CustomerID`
+ LIMIT 2 OFFSET 2
+ ) AS `t0`
+ ORDER BY `t`.`CustomerID`, `t0`.`CustomerID`
+ LIMIT @__p_1
+) AS `t1`
+LEFT JOIN `Orders` AS `o0` ON `t1`.`CustomerID` = `o0`.`CustomerID`
+LEFT JOIN `Orders` AS `o1` ON `t1`.`CustomerID0` = `o1`.`CustomerID`
+ORDER BY `t1`.`CustomerID`, `t1`.`CustomerID0`, `o0`.`OrderID`
+""");
+ }
+
+ public override async Task Include_reference(bool async)
+ {
+ await base.Include_reference(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+FROM `Orders` AS `o`
+LEFT JOIN `Customers` AS `c` ON `o`.`CustomerID` = `c`.`CustomerID`
+WHERE `o`.`CustomerID` IS NOT NULL AND (`o`.`CustomerID` LIKE 'F%')
+""");
+ }
+
+ public override async Task Include_multiple_references_and_collection_multi_level_reverse(bool async)
+ {
+ await base.Include_multiple_references_and_collection_multi_level_reverse(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`, `p`.`ProductID`, `p`.`Discontinued`, `p`.`ProductName`, `p`.`SupplierID`, `p`.`UnitPrice`, `p`.`UnitsInStock`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`, `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `o1`.`OrderID`, `o1`.`CustomerID`, `o1`.`EmployeeID`, `o1`.`OrderDate`
+FROM `Order Details` AS `o`
+INNER JOIN `Products` AS `p` ON `o`.`ProductID` = `p`.`ProductID`
+INNER JOIN `Orders` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+LEFT JOIN `Customers` AS `c` ON `o0`.`CustomerID` = `c`.`CustomerID`
+LEFT JOIN `Orders` AS `o1` ON `c`.`CustomerID` = `o1`.`CustomerID`
+WHERE (`o`.`OrderID` % 23) = 13
+ORDER BY `o`.`OrderID`, `o`.`ProductID`, `p`.`ProductID`, `o0`.`OrderID`, `c`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_closes_reader(bool async)
+ {
+ await base.Include_closes_reader(async);
+
+ AssertSql(
+"""
+SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+FROM (
+ 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`
+ LIMIT 1
+) AS `t`
+LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID`
+ORDER BY `t`.`CustomerID`
+""",
+ //
+ """
+SELECT `p`.`ProductID`, `p`.`Discontinued`, `p`.`ProductName`, `p`.`SupplierID`, `p`.`UnitPrice`, `p`.`UnitsInStock`
+FROM `Products` AS `p`
+""");
+ }
+
+ public override async Task Include_with_skip(bool async)
+ {
+ await base.Include_with_skip(async);
+
+ AssertSql(
+"""
+@__p_0='80'
+
+SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+FROM (
+ 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`
+ ORDER BY `c`.`ContactName`
+ LIMIT 18446744073709551610 OFFSET @__p_0
+) AS `t`
+LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID`
+ORDER BY `t`.`ContactName`, `t`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_collection_Join_GroupBy_Select(bool async)
+ {
+ await base.Include_collection_Join_GroupBy_Select(async);
+
+ AssertSql(
+"""
+SELECT `t0`.`OrderID`, `t0`.`CustomerID`, `t0`.`EmployeeID`, `t0`.`OrderDate`, `t`.`OrderID`, `t0`.`OrderID0`, `t0`.`ProductID`, `o3`.`OrderID`, `o3`.`ProductID`, `o3`.`Discount`, `o3`.`Quantity`, `o3`.`UnitPrice`
+FROM (
+ SELECT `o`.`OrderID`
+ FROM `Orders` AS `o`
+ INNER JOIN `Order Details` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+ WHERE `o`.`OrderID` = 10248
+ GROUP BY `o`.`OrderID`
+) AS `t`
+LEFT JOIN (
+ SELECT `t1`.`OrderID`, `t1`.`CustomerID`, `t1`.`EmployeeID`, `t1`.`OrderDate`, `t1`.`OrderID0`, `t1`.`ProductID`
+ FROM (
+ SELECT `o1`.`OrderID`, `o1`.`CustomerID`, `o1`.`EmployeeID`, `o1`.`OrderDate`, `o2`.`OrderID` AS `OrderID0`, `o2`.`ProductID`, ROW_NUMBER() OVER(PARTITION BY `o1`.`OrderID` ORDER BY `o1`.`OrderID`) AS `row`
+ FROM `Orders` AS `o1`
+ INNER JOIN `Order Details` AS `o2` ON `o1`.`OrderID` = `o2`.`OrderID`
+ WHERE `o1`.`OrderID` = 10248
+ ) AS `t1`
+ WHERE `t1`.`row` <= 1
+) AS `t0` ON `t`.`OrderID` = `t0`.`OrderID`
+LEFT JOIN `Order Details` AS `o3` ON `t0`.`OrderID` = `o3`.`OrderID`
+ORDER BY `t`.`OrderID`, `t0`.`OrderID`, `t0`.`OrderID0`, `t0`.`ProductID`, `o3`.`OrderID`
+""");
+ }
+
+ public override async Task Include_collection_GroupBy_Select(bool async)
+ {
+ await base.Include_collection_GroupBy_Select(async);
+
+ AssertSql(
+"""
+SELECT `t0`.`OrderID`, `t0`.`CustomerID`, `t0`.`EmployeeID`, `t0`.`OrderDate`, `t`.`OrderID`, `o1`.`OrderID`, `o1`.`ProductID`, `o1`.`Discount`, `o1`.`Quantity`, `o1`.`UnitPrice`
+FROM (
+ SELECT `o`.`OrderID`
+ FROM `Orders` AS `o`
+ WHERE `o`.`OrderID` = 10248
+ GROUP BY `o`.`OrderID`
+) AS `t`
+LEFT JOIN (
+ SELECT `t1`.`OrderID`, `t1`.`CustomerID`, `t1`.`EmployeeID`, `t1`.`OrderDate`
+ FROM (
+ SELECT `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`, ROW_NUMBER() OVER(PARTITION BY `o0`.`OrderID` ORDER BY `o0`.`OrderID`) AS `row`
+ FROM `Orders` AS `o0`
+ WHERE `o0`.`OrderID` = 10248
+ ) AS `t1`
+ WHERE `t1`.`row` <= 1
+) AS `t0` ON `t`.`OrderID` = `t0`.`OrderID`
+LEFT JOIN `Order Details` AS `o1` ON `t0`.`OrderID` = `o1`.`OrderID`
+ORDER BY `t`.`OrderID`, `t0`.`OrderID`, `o1`.`OrderID`
+""");
+ }
+
+ public override async Task Include_collection_orderby_take(bool async)
+ {
+ await base.Include_collection_orderby_take(async);
+
+ AssertSql(
+"""
+@__p_0='5'
+
+SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+FROM (
+ 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`
+ ORDER BY `c`.`CustomerID`
+ LIMIT @__p_0
+) AS `t`
+LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID`
+ORDER BY `t`.`CustomerID`
+""");
+ }
+
+ public override async Task Join_Include_collection_GroupBy_Select(bool async)
+ {
+ await base.Join_Include_collection_GroupBy_Select(async);
+
+ AssertSql(
+"""
+SELECT `t0`.`OrderID`, `t0`.`CustomerID`, `t0`.`EmployeeID`, `t0`.`OrderDate`, `t`.`OrderID`, `t0`.`OrderID0`, `t0`.`ProductID`, `o3`.`OrderID`, `o3`.`ProductID`, `o3`.`Discount`, `o3`.`Quantity`, `o3`.`UnitPrice`
+FROM (
+ SELECT `o0`.`OrderID`
+ FROM `Order Details` AS `o`
+ INNER JOIN `Orders` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+ WHERE `o`.`OrderID` = 10248
+ GROUP BY `o0`.`OrderID`
+) AS `t`
+LEFT JOIN (
+ SELECT `t1`.`OrderID`, `t1`.`CustomerID`, `t1`.`EmployeeID`, `t1`.`OrderDate`, `t1`.`OrderID0`, `t1`.`ProductID`
+ FROM (
+ SELECT `o2`.`OrderID`, `o2`.`CustomerID`, `o2`.`EmployeeID`, `o2`.`OrderDate`, `o1`.`OrderID` AS `OrderID0`, `o1`.`ProductID`, ROW_NUMBER() OVER(PARTITION BY `o2`.`OrderID` ORDER BY `o2`.`OrderID`) AS `row`
+ FROM `Order Details` AS `o1`
+ INNER JOIN `Orders` AS `o2` ON `o1`.`OrderID` = `o2`.`OrderID`
+ WHERE `o1`.`OrderID` = 10248
+ ) AS `t1`
+ WHERE `t1`.`row` <= 1
+) AS `t0` ON `t`.`OrderID` = `t0`.`OrderID`
+LEFT JOIN `Order Details` AS `o3` ON `t0`.`OrderID` = `o3`.`OrderID`
+ORDER BY `t`.`OrderID`, `t0`.`OrderID0`, `t0`.`ProductID`, `t0`.`OrderID`, `o3`.`OrderID`
+""");
+ }
+
+ public override async Task Include_collection_order_by_non_key(bool async)
+ {
+ await base.Include_collection_order_by_non_key(async);
+
+ AssertSql(
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+FROM `Customers` AS `c`
+LEFT JOIN `Orders` AS `o` ON `c`.`CustomerID` = `o`.`CustomerID`
+WHERE `c`.`CustomerID` LIKE 'F%'
+ORDER BY `c`.`PostalCode`, `c`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_when_result_operator(bool async)
+ {
+ await base.Include_when_result_operator(async);
+
+ AssertSql(
+"""
+SELECT EXISTS (
+ SELECT 1
+ FROM `Customers` AS `c`)
+""");
+ }
+
+ public override async Task Include_duplicate_reference2(bool async)
+ {
+ await base.Include_duplicate_reference2(async);
+
+ AssertSql(
+"""
+@__p_0='2'
+
+SELECT `t`.`OrderID`, `t`.`CustomerID`, `t`.`EmployeeID`, `t`.`OrderDate`, `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `t0`.`OrderID`, `t0`.`CustomerID`, `t0`.`EmployeeID`, `t0`.`OrderDate`
+FROM (
+ SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+ FROM `Orders` AS `o`
+ ORDER BY `o`.`OrderID`
+ LIMIT @__p_0
+) AS `t`
+CROSS JOIN (
+ SELECT `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`
+ FROM `Orders` AS `o0`
+ ORDER BY `o0`.`OrderID`
+ LIMIT 2 OFFSET 2
+) AS `t0`
+LEFT JOIN `Customers` AS `c` ON `t`.`CustomerID` = `c`.`CustomerID`
+ORDER BY `t`.`OrderID`
+""");
+ }
+
+ public override async Task Include_collection_and_reference(bool async)
+ {
+ await base.Include_collection_and_reference(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, `c`.`CustomerID`, `o0`.`OrderID`, `o0`.`ProductID`, `o0`.`Discount`, `o0`.`Quantity`, `o0`.`UnitPrice`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+FROM `Orders` AS `o`
+LEFT JOIN `Customers` AS `c` ON `o`.`CustomerID` = `c`.`CustomerID`
+LEFT JOIN `Order Details` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+WHERE `o`.`CustomerID` IS NOT NULL AND (`o`.`CustomerID` LIKE 'F%')
+ORDER BY `o`.`OrderID`, `c`.`CustomerID`, `o0`.`OrderID`
+""");
+ }
+
+ public override async Task Include_multiple_references_multi_level(bool async)
+ {
+ await base.Include_multiple_references_multi_level(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`, `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `p`.`ProductID`, `p`.`Discontinued`, `p`.`ProductName`, `p`.`SupplierID`, `p`.`UnitPrice`, `p`.`UnitsInStock`
+FROM `Order Details` AS `o`
+INNER JOIN `Orders` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+LEFT JOIN `Customers` AS `c` ON `o0`.`CustomerID` = `c`.`CustomerID`
+INNER JOIN `Products` AS `p` ON `o`.`ProductID` = `p`.`ProductID`
+WHERE (`o`.`OrderID` % 23) = 13
+""");
+ }
+
+ public override async Task Include_references_and_collection_multi_level_predicate(bool async)
+ {
+ await base.Include_references_and_collection_multi_level_predicate(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`, `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `o1`.`OrderID`, `o1`.`CustomerID`, `o1`.`EmployeeID`, `o1`.`OrderDate`
+FROM `Order Details` AS `o`
+INNER JOIN `Orders` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+LEFT JOIN `Customers` AS `c` ON `o0`.`CustomerID` = `c`.`CustomerID`
+LEFT JOIN `Orders` AS `o1` ON `c`.`CustomerID` = `o1`.`CustomerID`
+WHERE `o`.`OrderID` = 10248
+ORDER BY `o`.`OrderID`, `o`.`ProductID`, `o0`.`OrderID`, `c`.`CustomerID`
+""");
+ }
+
+ public override async Task SelectMany_Include_collection_GroupBy_Select(bool async)
+ {
+ await base.SelectMany_Include_collection_GroupBy_Select(async);
+
+ AssertSql(
+"""
+SELECT `t0`.`OrderID`, `t0`.`CustomerID`, `t0`.`EmployeeID`, `t0`.`OrderDate`, `t`.`OrderID`, `t0`.`OrderID0`, `t0`.`ProductID`, `o3`.`OrderID`, `o3`.`ProductID`, `o3`.`Discount`, `o3`.`Quantity`, `o3`.`UnitPrice`
+FROM (
+ SELECT `o0`.`OrderID`
+ FROM `Order Details` AS `o`
+ CROSS JOIN `Orders` AS `o0`
+ WHERE `o`.`OrderID` = 10248
+ GROUP BY `o0`.`OrderID`
+) AS `t`
+LEFT JOIN (
+ SELECT `t1`.`OrderID`, `t1`.`CustomerID`, `t1`.`EmployeeID`, `t1`.`OrderDate`, `t1`.`OrderID0`, `t1`.`ProductID`
+ FROM (
+ SELECT `o2`.`OrderID`, `o2`.`CustomerID`, `o2`.`EmployeeID`, `o2`.`OrderDate`, `o1`.`OrderID` AS `OrderID0`, `o1`.`ProductID`, ROW_NUMBER() OVER(PARTITION BY `o2`.`OrderID` ORDER BY `o2`.`OrderID`) AS `row`
+ FROM `Order Details` AS `o1`
+ CROSS JOIN `Orders` AS `o2`
+ WHERE `o1`.`OrderID` = 10248
+ ) AS `t1`
+ WHERE `t1`.`row` <= 1
+) AS `t0` ON `t`.`OrderID` = `t0`.`OrderID`
+LEFT JOIN `Order Details` AS `o3` ON `t0`.`OrderID` = `o3`.`OrderID`
+ORDER BY `t`.`OrderID`, `t0`.`OrderID0`, `t0`.`ProductID`, `t0`.`OrderID`, `o3`.`OrderID`
+""");
+ }
+
+ public override async Task Include_collection_with_last(bool async)
+ {
+ await base.Include_collection_with_last(async);
+
+ AssertSql(
+"""
+SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+FROM (
+ 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`
+ ORDER BY `c`.`CompanyName` DESC
+ LIMIT 1
+) AS `t`
+LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID`
+ORDER BY `t`.`CompanyName` DESC, `t`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_collection_OrderBy_empty_list_does_not_contains(bool async)
+ {
+ await base.Include_collection_OrderBy_empty_list_does_not_contains(async);
+
+ AssertSql(
+"""
+@__p_1='1'
+
+SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+FROM (
+ 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 (SELECT 1)
+ LIMIT 18446744073709551610 OFFSET @__p_1
+) AS `t`
+LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID`
+ORDER BY `t`.`c`, `t`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_multiple_references_then_include_multi_level_reverse(bool async)
+ {
+ await base.Include_multiple_references_then_include_multi_level_reverse(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`, `p`.`ProductID`, `p`.`Discontinued`, `p`.`ProductName`, `p`.`SupplierID`, `p`.`UnitPrice`, `p`.`UnitsInStock`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`, `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+FROM `Order Details` AS `o`
+INNER JOIN `Products` AS `p` ON `o`.`ProductID` = `p`.`ProductID`
+INNER JOIN `Orders` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+LEFT JOIN `Customers` AS `c` ON `o0`.`CustomerID` = `c`.`CustomerID`
+WHERE (`o`.`OrderID` % 23) = 13
+""");
+ }
+
+ public override async Task Include_reference_and_collection(bool async)
+ {
+ await base.Include_reference_and_collection(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `o0`.`OrderID`, `o0`.`ProductID`, `o0`.`Discount`, `o0`.`Quantity`, `o0`.`UnitPrice`
+FROM `Orders` AS `o`
+LEFT JOIN `Customers` AS `c` ON `o`.`CustomerID` = `c`.`CustomerID`
+LEFT JOIN `Order Details` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+WHERE `o`.`CustomerID` IS NOT NULL AND (`o`.`CustomerID` LIKE 'F%')
+ORDER BY `o`.`OrderID`, `c`.`CustomerID`, `o0`.`OrderID`
+""");
+ }
+
+ public override async Task Include_is_not_ignored_when_projection_contains_client_method_and_complex_expression(bool async)
+ {
+ await base.Include_is_not_ignored_when_projection_contains_client_method_and_complex_expression(async);
+
+ AssertSql(
+"""
+SELECT `e0`.`EmployeeID` IS NOT NULL, `e`.`EmployeeID`, `e`.`City`, `e`.`Country`, `e`.`FirstName`, `e`.`ReportsTo`, `e`.`Title`, `e0`.`EmployeeID`, `e0`.`City`, `e0`.`Country`, `e0`.`FirstName`, `e0`.`ReportsTo`, `e0`.`Title`
+FROM `Employees` AS `e`
+LEFT JOIN `Employees` AS `e0` ON `e`.`ReportsTo` = `e0`.`EmployeeID`
+WHERE `e`.`EmployeeID` IN (1, 2)
+ORDER BY `e`.`EmployeeID`
+""");
+ }
+
+ public override async Task Include_reference_with_filter_reordered(bool async)
+ {
+ await base.Include_reference_with_filter_reordered(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+FROM `Orders` AS `o`
+LEFT JOIN `Customers` AS `c` ON `o`.`CustomerID` = `c`.`CustomerID`
+WHERE `o`.`CustomerID` = 'ALFKI'
+""");
+ }
+
+ public override async Task Include_collection_order_by_subquery(bool async)
+ {
+ await base.Include_collection_order_by_subquery(async);
+
+ AssertSql(
+"""
+SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`
+FROM (
+ SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, (
+ SELECT `o`.`OrderDate`
+ FROM `Orders` AS `o`
+ WHERE `c`.`CustomerID` = `o`.`CustomerID`
+ ORDER BY `o`.`EmployeeID`
+ LIMIT 1) AS `c`
+ FROM `Customers` AS `c`
+ WHERE `c`.`CustomerID` = 'ALFKI'
+ ORDER BY (
+ SELECT `o`.`OrderDate`
+ FROM `Orders` AS `o`
+ WHERE `c`.`CustomerID` = `o`.`CustomerID`
+ ORDER BY `o`.`EmployeeID`
+ LIMIT 1)
+ LIMIT 1
+) AS `t`
+LEFT JOIN `Orders` AS `o0` ON `t`.`CustomerID` = `o0`.`CustomerID`
+ORDER BY `t`.`c`, `t`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_reference_and_collection_order_by(bool async)
+ {
+ await base.Include_reference_and_collection_order_by(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`
+FROM `Orders` AS `o`
+LEFT JOIN `Customers` AS `c` ON `o`.`CustomerID` = `c`.`CustomerID`
+LEFT JOIN `Orders` AS `o0` ON `c`.`CustomerID` = `o0`.`CustomerID`
+WHERE `o`.`CustomerID` IS NOT NULL AND (`o`.`CustomerID` LIKE 'F%')
+ORDER BY `o`.`OrderID`, `c`.`CustomerID`
+""");
+ }
+
+ public override async Task Then_include_collection_order_by_collection_column(bool async)
+ {
+ await base.Then_include_collection_order_by_collection_column(async);
+
+ AssertSql(
+"""
+SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `t0`.`OrderID`, `t0`.`CustomerID`, `t0`.`EmployeeID`, `t0`.`OrderDate`, `t0`.`OrderID0`, `t0`.`ProductID`, `t0`.`Discount`, `t0`.`Quantity`, `t0`.`UnitPrice`
+FROM (
+ SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, (
+ SELECT `o`.`OrderDate`
+ FROM `Orders` AS `o`
+ WHERE `c`.`CustomerID` = `o`.`CustomerID`
+ ORDER BY `o`.`OrderDate` DESC
+ LIMIT 1) AS `c`
+ FROM `Customers` AS `c`
+ WHERE `c`.`CustomerID` LIKE 'W%'
+ ORDER BY (
+ SELECT `o`.`OrderDate`
+ FROM `Orders` AS `o`
+ WHERE `c`.`CustomerID` = `o`.`CustomerID`
+ ORDER BY `o`.`OrderDate` DESC
+ LIMIT 1) DESC
+ LIMIT 1
+) AS `t`
+LEFT JOIN (
+ SELECT `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`, `o1`.`OrderID` AS `OrderID0`, `o1`.`ProductID`, `o1`.`Discount`, `o1`.`Quantity`, `o1`.`UnitPrice`
+ FROM `Orders` AS `o0`
+ LEFT JOIN `Order Details` AS `o1` ON `o0`.`OrderID` = `o1`.`OrderID`
+) AS `t0` ON `t`.`CustomerID` = `t0`.`CustomerID`
+ORDER BY `t`.`c` DESC, `t`.`CustomerID`, `t0`.`OrderID`, `t0`.`OrderID0`
+""");
+ }
+
+ public override async Task Include_multiple_references_then_include_multi_level(bool async)
+ {
+ await base.Include_multiple_references_then_include_multi_level(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`, `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `p`.`ProductID`, `p`.`Discontinued`, `p`.`ProductName`, `p`.`SupplierID`, `p`.`UnitPrice`, `p`.`UnitsInStock`
+FROM `Order Details` AS `o`
+INNER JOIN `Orders` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+LEFT JOIN `Customers` AS `c` ON `o0`.`CustomerID` = `c`.`CustomerID`
+INNER JOIN `Products` AS `p` ON `o`.`ProductID` = `p`.`ProductID`
+WHERE (`o`.`OrderID` % 23) = 13
+""");
+ }
+
+ public override async Task Include_collection_skip_no_order_by(bool async)
+ {
+ await base.Include_collection_skip_no_order_by(async);
+
+ AssertSql(
+"""
+@__p_0='10'
+
+SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+FROM (
+ 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`
+ LIMIT 18446744073709551610 OFFSET @__p_0
+) AS `t`
+LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID`
+ORDER BY `t`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_multi_level_reference_then_include_collection_predicate(bool async)
+ {
+ await base.Include_multi_level_reference_then_include_collection_predicate(async);
+
+ AssertSql(
+"""
+SELECT `t`.`OrderID`, `t`.`CustomerID`, `t`.`EmployeeID`, `t`.`OrderDate`, `t`.`CustomerID0`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`
+FROM (
+ SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, `c`.`CustomerID` AS `CustomerID0`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`
+ FROM `Orders` AS `o`
+ LEFT JOIN `Customers` AS `c` ON `o`.`CustomerID` = `c`.`CustomerID`
+ WHERE `o`.`OrderID` = 10248
+ LIMIT 2
+) AS `t`
+LEFT JOIN `Orders` AS `o0` ON `t`.`CustomerID0` = `o0`.`CustomerID`
+ORDER BY `t`.`OrderID`, `t`.`CustomerID0`
+""");
+ }
+
+ public override async Task Include_multiple_references_and_collection_multi_level(bool async)
+ {
+ await base.Include_multiple_references_and_collection_multi_level(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`, `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `p`.`ProductID`, `o1`.`OrderID`, `o1`.`CustomerID`, `o1`.`EmployeeID`, `o1`.`OrderDate`, `p`.`Discontinued`, `p`.`ProductName`, `p`.`SupplierID`, `p`.`UnitPrice`, `p`.`UnitsInStock`
+FROM `Order Details` AS `o`
+INNER JOIN `Orders` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+LEFT JOIN `Customers` AS `c` ON `o0`.`CustomerID` = `c`.`CustomerID`
+INNER JOIN `Products` AS `p` ON `o`.`ProductID` = `p`.`ProductID`
+LEFT JOIN `Orders` AS `o1` ON `c`.`CustomerID` = `o1`.`CustomerID`
+WHERE (`o`.`OrderID` % 23) = 13
+ORDER BY `o`.`OrderID`, `o`.`ProductID`, `o0`.`OrderID`, `c`.`CustomerID`, `p`.`ProductID`
+""");
+ }
+
+ public override async Task Include_where_skip_take_projection(bool async)
+ {
+ await base.Include_where_skip_take_projection(async);
+
+ AssertSql(
+"""
+@__p_1='2'
+@__p_0='1'
+
+SELECT `o0`.`CustomerID`
+FROM (
+ SELECT `o`.`OrderID`, `o`.`ProductID`
+ FROM `Order Details` AS `o`
+ WHERE `o`.`Quantity` = 10
+ ORDER BY `o`.`OrderID`, `o`.`ProductID`
+ LIMIT @__p_1 OFFSET @__p_0
+) AS `t`
+INNER JOIN `Orders` AS `o0` ON `t`.`OrderID` = `o0`.`OrderID`
+ORDER BY `t`.`OrderID`, `t`.`ProductID`
+""");
+ }
+
+ public override async Task Include_with_take(bool async)
+ {
+ await base.Include_with_take(async);
+
+ AssertSql(
+"""
+@__p_0='10'
+
+SELECT `t`.`CustomerID`, `t`.`Address`, `t`.`City`, `t`.`CompanyName`, `t`.`ContactName`, `t`.`ContactTitle`, `t`.`Country`, `t`.`Fax`, `t`.`Phone`, `t`.`PostalCode`, `t`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+FROM (
+ 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`
+ ORDER BY `c`.`ContactName` DESC
+ LIMIT @__p_0
+) AS `t`
+LEFT JOIN `Orders` AS `o` ON `t`.`CustomerID` = `o`.`CustomerID`
+ORDER BY `t`.`ContactName` DESC, `t`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_multiple_references(bool async)
+ {
+ await base.Include_multiple_references(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`, `p`.`ProductID`, `p`.`Discontinued`, `p`.`ProductName`, `p`.`SupplierID`, `p`.`UnitPrice`, `p`.`UnitsInStock`
+FROM `Order Details` AS `o`
+INNER JOIN `Orders` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+INNER JOIN `Products` AS `p` ON `o`.`ProductID` = `p`.`ProductID`
+WHERE (`o`.`OrderID` % 23) = 13
+""");
+ }
+
+ public override async Task Include_list(bool async)
+ {
+ await base.Include_list(async);
+
+ AssertSql(
+"""
+SELECT `p`.`ProductID`, `p`.`Discontinued`, `p`.`ProductName`, `p`.`SupplierID`, `p`.`UnitPrice`, `p`.`UnitsInStock`, `t`.`OrderID`, `t`.`ProductID`, `t`.`Discount`, `t`.`Quantity`, `t`.`UnitPrice`, `t`.`OrderID0`, `t`.`CustomerID`, `t`.`EmployeeID`, `t`.`OrderDate`
+FROM `Products` AS `p`
+LEFT JOIN (
+ SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`, `o0`.`OrderID` AS `OrderID0`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`
+ FROM `Order Details` AS `o`
+ INNER JOIN `Orders` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+) AS `t` ON `p`.`ProductID` = `t`.`ProductID`
+WHERE ((`p`.`ProductID` % 17) = 5) AND (`p`.`UnitPrice` < 20.0)
+ORDER BY `p`.`ProductID`, `t`.`OrderID`, `t`.`ProductID`
+""");
+ }
+
+ public override async Task Include_empty_reference_sets_IsLoaded(bool async)
+ {
+ await base.Include_empty_reference_sets_IsLoaded(async);
+
+ AssertSql(
+"""
+SELECT `e`.`EmployeeID`, `e`.`City`, `e`.`Country`, `e`.`FirstName`, `e`.`ReportsTo`, `e`.`Title`, `e0`.`EmployeeID`, `e0`.`City`, `e0`.`Country`, `e0`.`FirstName`, `e0`.`ReportsTo`, `e0`.`Title`
+FROM `Employees` AS `e`
+LEFT JOIN `Employees` AS `e0` ON `e`.`ReportsTo` = `e0`.`EmployeeID`
+WHERE `e0`.`EmployeeID` IS NULL
+LIMIT 1
+""");
+ }
+
+ public override async Task Include_references_then_include_collection_multi_level_predicate(bool async)
+ {
+ await base.Include_references_then_include_collection_multi_level_predicate(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`, `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `o1`.`OrderID`, `o1`.`CustomerID`, `o1`.`EmployeeID`, `o1`.`OrderDate`
+FROM `Order Details` AS `o`
+INNER JOIN `Orders` AS `o0` ON `o`.`OrderID` = `o0`.`OrderID`
+LEFT JOIN `Customers` AS `c` ON `o0`.`CustomerID` = `c`.`CustomerID`
+LEFT JOIN `Orders` AS `o1` ON `c`.`CustomerID` = `o1`.`CustomerID`
+WHERE `o`.`OrderID` = 10248
+ORDER BY `o`.`OrderID`, `o`.`ProductID`, `o0`.`OrderID`, `c`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_collection_with_conditional_order_by(bool async)
+ {
+ await base.Include_collection_with_conditional_order_by(async);
+
+ AssertSql(
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+FROM `Customers` AS `c`
+LEFT JOIN `Orders` AS `o` ON `c`.`CustomerID` = `o`.`CustomerID`
+WHERE `c`.`CustomerID` LIKE 'F%'
+ORDER BY CASE
+ WHEN `c`.`CustomerID` LIKE 'S%' THEN 1
+ ELSE 2
+END, `c`.`CustomerID`
+""");
+ }
+
+ public override async Task Include_non_existing_navigation(bool async)
+ {
+ await base.Include_non_existing_navigation(async);
+
+ AssertSql();
+ }
+
+ public override async Task Include_property(bool async)
+ {
+ await base.Include_property(async);
+
+ AssertSql();
+ }
+
+ public override async Task Include_property_after_navigation(bool async)
+ {
+ await base.Include_property_after_navigation(async);
+
+ AssertSql();
+ }
+
+ public override async Task Include_property_expression_invalid(bool async)
+ {
+ await base.Include_property_expression_invalid(async);
+
+ AssertSql();
+ }
+
+ public override async Task Then_include_property_expression_invalid(bool async)
+ {
+ await base.Then_include_property_expression_invalid(async);
+
+ AssertSql();
+ }
+
+ public override async Task Filtered_include_with_multiple_ordering(bool async)
+ {
+ await base.Filtered_include_with_multiple_ordering(async);
+
+ AssertSql(
+"""
+SELECT `c`.`CustomerID`, `c`.`Address`, `c`.`City`, `c`.`CompanyName`, `c`.`ContactName`, `c`.`ContactTitle`, `c`.`Country`, `c`.`Fax`, `c`.`Phone`, `c`.`PostalCode`, `c`.`Region`, `t`.`OrderID`, `t`.`CustomerID`, `t`.`EmployeeID`, `t`.`OrderDate`
+FROM `Customers` AS `c`
+LEFT JOIN LATERAL (
+ SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+ FROM `Orders` AS `o`
+ WHERE `c`.`CustomerID` = `o`.`CustomerID`
+ ORDER BY `o`.`OrderID`
+ LIMIT 18446744073709551610 OFFSET 1
+) AS `t` ON TRUE
+WHERE `c`.`CustomerID` LIKE 'F%'
+ORDER BY `c`.`CustomerID`, `t`.`OrderDate` DESC
+""");
+ }
+
+ public override async Task Include_specified_on_non_entity_not_supported(bool async)
+ {
+ await base.Include_specified_on_non_entity_not_supported(async);
+
+ AssertSql();
+ }
+
+ public override async Task Include_collection_with_client_filter(bool async)
+ {
+ await base.Include_collection_with_client_filter(async);
+
+ AssertSql();
+ }
+
+ private void AssertSql(params string[] expected)
+ => Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
+}
diff --git a/test/EFCore.MySql.FunctionalTests/Query/NorthwindFunctionsQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/NorthwindFunctionsQueryMySqlTest.cs
index f3bbe9be0..ba6779ac2 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/NorthwindFunctionsQueryMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/NorthwindFunctionsQueryMySqlTest.cs
@@ -173,9 +173,9 @@ public override async Task Indexof_with_emptystring(bool async)
await base.Indexof_with_emptystring(async);
AssertSql(
- $@"SELECT LOCATE('', `c`.`ContactName`) - 1
+ @"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`.`CustomerID` = 'ALFKI'");
+WHERE (LOCATE('', `c`.`ContactName`) - 1) = 0");
}
[ConditionalTheory]
@@ -184,9 +184,9 @@ public override async Task Replace_with_emptystring(bool async)
await base.Replace_with_emptystring(async);
AssertSql(
- $@"SELECT REPLACE(`c`.`ContactName`, 'ari', '')
+ @"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`.`CustomerID` = 'ALFKI'");
+WHERE REPLACE(`c`.`ContactName`, 'ia', '') = 'Mar Anders'");
}
[ConditionalTheory]
@@ -342,7 +342,7 @@ public override async Task Select_math_round_int(bool async)
await base.Select_math_round_int(async);
AssertSql(
- $@"SELECT ROUND({CastAsDouble("`o`.`OrderID`")}) AS `A`
+ $@"SELECT ROUND({MySqlTestHelpers.CastAsDouble("`o`.`OrderID`")}) AS `A`
FROM `Orders` AS `o`
WHERE `o`.`OrderID` < 10250");
}
@@ -543,7 +543,7 @@ public override async Task Where_math_ceiling1(bool async)
AssertSql(
$@"SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
FROM `Order Details` AS `o`
-WHERE (`o`.`UnitPrice` < 7.0) AND (CEILING({CastAsDouble("`o`.`Discount`")}) > 0.0)");
+WHERE (`o`.`UnitPrice` < 7.0) AND (CEILING({MySqlTestHelpers.CastAsDouble("`o`.`Discount`")}) > 0.0)");
}
public override async Task Where_math_ceiling2(bool async)
@@ -573,7 +573,7 @@ public override async Task Where_math_power(bool async)
AssertSql(
$@"SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
FROM `Order Details` AS `o`
-WHERE POWER({CastAsDouble("`o`.`Discount`")}, 3.0) > 0.004999999888241291");
+WHERE POWER({MySqlTestHelpers.CastAsDouble("`o`.`Discount`")}, 3.0) > 0.004999999888241291");
}
public override async Task Where_math_round(bool async)
@@ -591,7 +591,7 @@ public override async Task Select_math_truncate_int(bool async)
await base.Select_math_truncate_int(async);
AssertSql(
- $@"SELECT TRUNCATE({CastAsDouble("`o`.`OrderID`")}, 0) AS `A`
+ $@"SELECT TRUNCATE({MySqlTestHelpers.CastAsDouble("`o`.`OrderID`")}, 0) AS `A`
FROM `Orders` AS `o`
WHERE `o`.`OrderID` < 10250");
}
@@ -623,7 +623,7 @@ public override async Task Where_math_exp(bool async)
AssertSql(
$@"SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
FROM `Order Details` AS `o`
-WHERE (`o`.`OrderID` = 11077) AND (EXP({CastAsDouble("`o`.`Discount`")}) > 1.0)");
+WHERE (`o`.`OrderID` = 11077) AND (EXP({MySqlTestHelpers.CastAsDouble("`o`.`Discount`")}) > 1.0)");
}
public override async Task Where_math_log10(bool async)
@@ -633,7 +633,7 @@ public override async Task Where_math_log10(bool async)
AssertSql(
$@"SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
FROM `Order Details` AS `o`
-WHERE ((`o`.`OrderID` = 11077) AND (`o`.`Discount` > 0)) AND (LOG10({CastAsDouble("`o`.`Discount`")}) < 0.0)");
+WHERE ((`o`.`OrderID` = 11077) AND (`o`.`Discount` > 0)) AND (LOG10({MySqlTestHelpers.CastAsDouble("`o`.`Discount`")}) < 0.0)");
}
public override async Task Where_math_log(bool async)
@@ -643,7 +643,7 @@ public override async Task Where_math_log(bool async)
AssertSql(
$@"SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
FROM `Order Details` AS `o`
-WHERE ((`o`.`OrderID` = 11077) AND (`o`.`Discount` > 0)) AND (LOG({CastAsDouble("`o`.`Discount`")}) < 0.0)");
+WHERE ((`o`.`OrderID` = 11077) AND (`o`.`Discount` > 0)) AND (LOG({MySqlTestHelpers.CastAsDouble("`o`.`Discount`")}) < 0.0)");
}
public override async Task Where_math_log_new_base(bool async)
@@ -653,7 +653,7 @@ public override async Task Where_math_log_new_base(bool async)
AssertSql(
$@"SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
FROM `Order Details` AS `o`
-WHERE ((`o`.`OrderID` = 11077) AND (`o`.`Discount` > 0)) AND (LOG({CastAsDouble("`o`.`Discount`")}, 7.0) < 0.0)");
+WHERE ((`o`.`OrderID` = 11077) AND (`o`.`Discount` > 0)) AND (LOG({MySqlTestHelpers.CastAsDouble("`o`.`Discount`")}, 7.0) < 0.0)");
}
public override async Task Where_math_sqrt(bool async)
@@ -663,7 +663,7 @@ public override async Task Where_math_sqrt(bool async)
AssertSql(
$@"SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
FROM `Order Details` AS `o`
-WHERE (`o`.`OrderID` = 11077) AND (SQRT({CastAsDouble("`o`.`Discount`")}) > 0.0)");
+WHERE (`o`.`OrderID` = 11077) AND (SQRT({MySqlTestHelpers.CastAsDouble("`o`.`Discount`")}) > 0.0)");
}
public override async Task Where_math_acos(bool async)
@@ -673,7 +673,7 @@ public override async Task Where_math_acos(bool async)
AssertSql(
$@"SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
FROM `Order Details` AS `o`
-WHERE (`o`.`OrderID` = 11077) AND (ACOS({CastAsDouble("`o`.`Discount`")}) > 1.0)");
+WHERE (`o`.`OrderID` = 11077) AND (ACOS({MySqlTestHelpers.CastAsDouble("`o`.`Discount`")}) > 1.0)");
}
public override async Task Where_math_asin(bool async)
@@ -683,7 +683,7 @@ public override async Task Where_math_asin(bool async)
AssertSql(
$@"SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
FROM `Order Details` AS `o`
-WHERE (`o`.`OrderID` = 11077) AND (ASIN({CastAsDouble("`o`.`Discount`")}) > 0.0)");
+WHERE (`o`.`OrderID` = 11077) AND (ASIN({MySqlTestHelpers.CastAsDouble("`o`.`Discount`")}) > 0.0)");
}
public override async Task Where_math_atan(bool async)
@@ -693,7 +693,7 @@ public override async Task Where_math_atan(bool async)
AssertSql(
$@"SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
FROM `Order Details` AS `o`
-WHERE (`o`.`OrderID` = 11077) AND (ATAN({CastAsDouble("`o`.`Discount`")}) > 0.0)");
+WHERE (`o`.`OrderID` = 11077) AND (ATAN({MySqlTestHelpers.CastAsDouble("`o`.`Discount`")}) > 0.0)");
}
public override async Task Where_math_atan2(bool async)
@@ -703,7 +703,7 @@ public override async Task Where_math_atan2(bool async)
AssertSql(
$@"SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
FROM `Order Details` AS `o`
-WHERE (`o`.`OrderID` = 11077) AND (ATAN2({CastAsDouble("`o`.`Discount`")}, 1.0) > 0.0)");
+WHERE (`o`.`OrderID` = 11077) AND (ATAN2({MySqlTestHelpers.CastAsDouble("`o`.`Discount`")}, 1.0) > 0.0)");
}
public override async Task Where_math_cos(bool async)
@@ -713,7 +713,7 @@ public override async Task Where_math_cos(bool async)
AssertSql(
$@"SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
FROM `Order Details` AS `o`
-WHERE (`o`.`OrderID` = 11077) AND (COS({CastAsDouble("`o`.`Discount`")}) > 0.0)");
+WHERE (`o`.`OrderID` = 11077) AND (COS({MySqlTestHelpers.CastAsDouble("`o`.`Discount`")}) > 0.0)");
}
public override async Task Where_math_sin(bool async)
@@ -723,7 +723,7 @@ public override async Task Where_math_sin(bool async)
AssertSql(
$@"SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
FROM `Order Details` AS `o`
-WHERE (`o`.`OrderID` = 11077) AND (SIN({CastAsDouble("`o`.`Discount`")}) > 0.0)");
+WHERE (`o`.`OrderID` = 11077) AND (SIN({MySqlTestHelpers.CastAsDouble("`o`.`Discount`")}) > 0.0)");
}
public override async Task Where_math_tan(bool async)
@@ -733,7 +733,7 @@ public override async Task Where_math_tan(bool async)
AssertSql(
$@"SELECT `o`.`OrderID`, `o`.`ProductID`, `o`.`Discount`, `o`.`Quantity`, `o`.`UnitPrice`
FROM `Order Details` AS `o`
-WHERE (`o`.`OrderID` = 11077) AND (TAN({CastAsDouble("`o`.`Discount`")}) > 0.0)");
+WHERE (`o`.`OrderID` = 11077) AND (TAN({MySqlTestHelpers.CastAsDouble("`o`.`Discount`")}) > 0.0)");
}
public override async Task Where_math_sign(bool async)
@@ -763,7 +763,7 @@ public override async Task Where_functions_nested(bool async)
AssertSql(
$@"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 POWER({CastAsDouble("CHAR_LENGTH(`c`.`CustomerID`)")}, 2.0) = 25.0");
+WHERE POWER({MySqlTestHelpers.CastAsDouble("CHAR_LENGTH(`c`.`CustomerID`)")}, 2.0) = 25.0");
}
public override async Task IsNullOrEmpty_in_predicate(bool async)
@@ -854,10 +854,10 @@ public override async Task Projecting_Math_Truncate_and_ordering_by_it_twice(boo
await base.Projecting_Math_Truncate_and_ordering_by_it_twice(async);
AssertSql(
- $@"SELECT TRUNCATE({CastAsDouble("`o`.`OrderID`")}, 0) AS `A`
+ $@"SELECT TRUNCATE({MySqlTestHelpers.CastAsDouble("`o`.`OrderID`")}, 0) AS `A`
FROM `Orders` AS `o`
WHERE `o`.`OrderID` < 10250
-ORDER BY TRUNCATE({CastAsDouble("`o`.`OrderID`")}, 0)");
+ORDER BY TRUNCATE({MySqlTestHelpers.CastAsDouble("`o`.`OrderID`")}, 0)");
}
public override async Task Projecting_Math_Truncate_and_ordering_by_it_twice2(bool async)
@@ -865,10 +865,10 @@ public override async Task Projecting_Math_Truncate_and_ordering_by_it_twice2(bo
await base.Projecting_Math_Truncate_and_ordering_by_it_twice2(async);
AssertSql(
- $@"SELECT TRUNCATE({CastAsDouble("`o`.`OrderID`")}, 0) AS `A`
+ $@"SELECT TRUNCATE({MySqlTestHelpers.CastAsDouble("`o`.`OrderID`")}, 0) AS `A`
FROM `Orders` AS `o`
WHERE `o`.`OrderID` < 10250
-ORDER BY TRUNCATE({CastAsDouble("`o`.`OrderID`")}, 0) DESC");
+ORDER BY TRUNCATE({MySqlTestHelpers.CastAsDouble("`o`.`OrderID`")}, 0) DESC");
}
public override async Task Projecting_Math_Truncate_and_ordering_by_it_twice3(bool async)
@@ -876,10 +876,10 @@ public override async Task Projecting_Math_Truncate_and_ordering_by_it_twice3(bo
await base.Projecting_Math_Truncate_and_ordering_by_it_twice3(async);
AssertSql(
- $@"SELECT TRUNCATE({CastAsDouble("`o`.`OrderID`")}, 0) AS `A`
+ $@"SELECT TRUNCATE({MySqlTestHelpers.CastAsDouble("`o`.`OrderID`")}, 0) AS `A`
FROM `Orders` AS `o`
WHERE `o`.`OrderID` < 10250
-ORDER BY TRUNCATE({CastAsDouble("`o`.`OrderID`")}, 0) DESC");
+ORDER BY TRUNCATE({MySqlTestHelpers.CastAsDouble("`o`.`OrderID`")}, 0) DESC");
}
public override async Task String_Compare_simple_zero(bool async)
@@ -1379,11 +1379,11 @@ public override async Task Convert_ToBoolean(bool async)
//
$@"SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
FROM `Orders` AS `o`
-WHERE (`o`.`CustomerID` = 'ALFKI') AND CAST({CastAsDouble("`o`.`OrderID` % 3")} AS signed)",
+WHERE (`o`.`CustomerID` = 'ALFKI') AND CAST({MySqlTestHelpers.CastAsDouble("`o`.`OrderID` % 3")} AS signed)",
//
$@"SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
FROM `Orders` AS `o`
-WHERE (`o`.`CustomerID` = 'ALFKI') AND CAST({CastAsDouble("`o`.`OrderID` % 3")} AS signed)",
+WHERE (`o`.`CustomerID` = 'ALFKI') AND CAST({MySqlTestHelpers.CastAsDouble("`o`.`OrderID` % 3")} AS signed)",
//
$@"SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
FROM `Orders` AS `o`
@@ -1417,11 +1417,11 @@ public override async Task Convert_ToByte(bool async)
//
$@"SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
FROM `Orders` AS `o`
-WHERE (`o`.`CustomerID` = 'ALFKI') AND (CAST({CastAsDouble("`o`.`OrderID` % 1")} AS unsigned) >= 0)",
+WHERE (`o`.`CustomerID` = 'ALFKI') AND (CAST({MySqlTestHelpers.CastAsDouble("`o`.`OrderID` % 1")} AS unsigned) >= 0)",
//
$@"SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
FROM `Orders` AS `o`
-WHERE (`o`.`CustomerID` = 'ALFKI') AND (CAST({CastAsDouble("`o`.`OrderID` % 1")} AS unsigned) >= 0)",
+WHERE (`o`.`CustomerID` = 'ALFKI') AND (CAST({MySqlTestHelpers.CastAsDouble("`o`.`OrderID` % 1")} AS unsigned) >= 0)",
//
$@"SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
FROM `Orders` AS `o`
@@ -1459,11 +1459,11 @@ public override async Task Convert_ToDecimal(bool async)
//
$@"SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
FROM `Orders` AS `o`
-WHERE (`o`.`CustomerID` = 'ALFKI') AND (CAST({CastAsDouble("`o`.`OrderID` % 1")} AS decimal(65,30)) >= 0.0)",
+WHERE (`o`.`CustomerID` = 'ALFKI') AND (CAST({MySqlTestHelpers.CastAsDouble("`o`.`OrderID` % 1")} AS decimal(65,30)) >= 0.0)",
//
$@"SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
FROM `Orders` AS `o`
-WHERE (`o`.`CustomerID` = 'ALFKI') AND (CAST({CastAsDouble("`o`.`OrderID` % 1")} AS decimal(65,30)) >= 0.0)",
+WHERE (`o`.`CustomerID` = 'ALFKI') AND (CAST({MySqlTestHelpers.CastAsDouble("`o`.`OrderID` % 1")} AS decimal(65,30)) >= 0.0)",
//
$@"SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
FROM `Orders` AS `o`
@@ -1489,39 +1489,39 @@ public override async Task Convert_ToDouble(bool async)
AssertSql(
$@"SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
FROM `Orders` AS `o`
-WHERE (`o`.`CustomerID` = 'ALFKI') AND ({CastAsDouble("CAST(`o`.`OrderID` % 1 AS signed)")} >= 0.0)",
+WHERE (`o`.`CustomerID` = 'ALFKI') AND ({MySqlTestHelpers.CastAsDouble("CAST(`o`.`OrderID` % 1 AS signed)")} >= 0.0)",
//
$@"SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
FROM `Orders` AS `o`
-WHERE (`o`.`CustomerID` = 'ALFKI') AND ({CastAsDouble("CAST(`o`.`OrderID` % 1 AS unsigned)")} >= 0.0)",
+WHERE (`o`.`CustomerID` = 'ALFKI') AND ({MySqlTestHelpers.CastAsDouble("CAST(`o`.`OrderID` % 1 AS unsigned)")} >= 0.0)",
//
$@"SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
FROM `Orders` AS `o`
-WHERE (`o`.`CustomerID` = 'ALFKI') AND ({CastAsDouble("CAST(`o`.`OrderID` % 1 AS decimal(65,30))")} >= 0.0)",
+WHERE (`o`.`CustomerID` = 'ALFKI') AND ({MySqlTestHelpers.CastAsDouble("CAST(`o`.`OrderID` % 1 AS decimal(65,30))")} >= 0.0)",
//
$@"SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
FROM `Orders` AS `o`
-WHERE (`o`.`CustomerID` = 'ALFKI') AND ({CastAsDouble("`o`.`OrderID` % 1")} >= 0.0)",
+WHERE (`o`.`CustomerID` = 'ALFKI') AND ({MySqlTestHelpers.CastAsDouble("`o`.`OrderID` % 1")} >= 0.0)",
//
$@"SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
FROM `Orders` AS `o`
-WHERE (`o`.`CustomerID` = 'ALFKI') AND ({CastAsDouble("`o`.`OrderID` % 1")} >= 0.0)",
+WHERE (`o`.`CustomerID` = 'ALFKI') AND ({MySqlTestHelpers.CastAsDouble("`o`.`OrderID` % 1")} >= 0.0)",
//
$@"SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
FROM `Orders` AS `o`
-WHERE (`o`.`CustomerID` = 'ALFKI') AND ({CastAsDouble("CAST(`o`.`OrderID` % 1 AS signed)")} >= 0.0)",
+WHERE (`o`.`CustomerID` = 'ALFKI') AND ({MySqlTestHelpers.CastAsDouble("CAST(`o`.`OrderID` % 1 AS signed)")} >= 0.0)",
//
$@"SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
FROM `Orders` AS `o`
-WHERE (`o`.`CustomerID` = 'ALFKI') AND ({CastAsDouble("CAST(`o`.`OrderID` % 1 AS signed)")} >= 0.0)",
+WHERE (`o`.`CustomerID` = 'ALFKI') AND ({MySqlTestHelpers.CastAsDouble("CAST(`o`.`OrderID` % 1 AS signed)")} >= 0.0)",
//
$@"SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
FROM `Orders` AS `o`
-WHERE (`o`.`CustomerID` = 'ALFKI') AND ({CastAsDouble("CAST(`o`.`OrderID` % 1 AS signed)")} >= 0.0)",
+WHERE (`o`.`CustomerID` = 'ALFKI') AND ({MySqlTestHelpers.CastAsDouble("CAST(`o`.`OrderID` % 1 AS signed)")} >= 0.0)",
//
$@"SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
FROM `Orders` AS `o`
-WHERE (`o`.`CustomerID` = 'ALFKI') AND ({CastAsDouble("CAST(`o`.`OrderID` % 1 AS char)")} >= 0.0)");
+WHERE (`o`.`CustomerID` = 'ALFKI') AND ({MySqlTestHelpers.CastAsDouble("CAST(`o`.`OrderID` % 1 AS char)")} >= 0.0)");
}
public override async Task Convert_ToInt16(bool async)
@@ -1543,11 +1543,11 @@ public override async Task Convert_ToInt16(bool async)
//
$@"SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
FROM `Orders` AS `o`
-WHERE (`o`.`CustomerID` = 'ALFKI') AND (CAST({CastAsDouble("`o`.`OrderID` % 1")} AS signed) >= 0)",
+WHERE (`o`.`CustomerID` = 'ALFKI') AND (CAST({MySqlTestHelpers.CastAsDouble("`o`.`OrderID` % 1")} AS signed) >= 0)",
//
$@"SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
FROM `Orders` AS `o`
-WHERE (`o`.`CustomerID` = 'ALFKI') AND (CAST({CastAsDouble("`o`.`OrderID` % 1")} AS signed) >= 0)",
+WHERE (`o`.`CustomerID` = 'ALFKI') AND (CAST({MySqlTestHelpers.CastAsDouble("`o`.`OrderID` % 1")} AS signed) >= 0)",
//
$@"SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
FROM `Orders` AS `o`
@@ -1585,11 +1585,11 @@ public override async Task Convert_ToInt32(bool async)
//
$@"SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
FROM `Orders` AS `o`
-WHERE (`o`.`CustomerID` = 'ALFKI') AND (CAST({CastAsDouble("`o`.`OrderID` % 1")} AS signed) >= 0)",
+WHERE (`o`.`CustomerID` = 'ALFKI') AND (CAST({MySqlTestHelpers.CastAsDouble("`o`.`OrderID` % 1")} AS signed) >= 0)",
//
$@"SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
FROM `Orders` AS `o`
-WHERE (`o`.`CustomerID` = 'ALFKI') AND (CAST({CastAsDouble("`o`.`OrderID` % 1")} AS signed) >= 0)",
+WHERE (`o`.`CustomerID` = 'ALFKI') AND (CAST({MySqlTestHelpers.CastAsDouble("`o`.`OrderID` % 1")} AS signed) >= 0)",
//
$@"SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
FROM `Orders` AS `o`
@@ -1627,11 +1627,11 @@ public override async Task Convert_ToInt64(bool async)
//
$@"SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
FROM `Orders` AS `o`
-WHERE (`o`.`CustomerID` = 'ALFKI') AND (CAST({CastAsDouble("`o`.`OrderID` % 1")} AS signed) >= 0)",
+WHERE (`o`.`CustomerID` = 'ALFKI') AND (CAST({MySqlTestHelpers.CastAsDouble("`o`.`OrderID` % 1")} AS signed) >= 0)",
//
$@"SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
FROM `Orders` AS `o`
-WHERE (`o`.`CustomerID` = 'ALFKI') AND (CAST({CastAsDouble("`o`.`OrderID` % 1")} AS signed) >= 0)",
+WHERE (`o`.`CustomerID` = 'ALFKI') AND (CAST({MySqlTestHelpers.CastAsDouble("`o`.`OrderID` % 1")} AS signed) >= 0)",
//
$@"SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
FROM `Orders` AS `o`
@@ -1669,11 +1669,11 @@ public override async Task Convert_ToString(bool async)
//
$@"SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
FROM `Orders` AS `o`
-WHERE (`o`.`CustomerID` = 'ALFKI') AND (CAST({CastAsDouble("`o`.`OrderID` % 1")} AS char) <> '10')",
+WHERE (`o`.`CustomerID` = 'ALFKI') AND (CAST({MySqlTestHelpers.CastAsDouble("`o`.`OrderID` % 1")} AS char) <> '10')",
//
$@"SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
FROM `Orders` AS `o`
-WHERE (`o`.`CustomerID` = 'ALFKI') AND (CAST({CastAsDouble("`o`.`OrderID` % 1")} AS char) <> '10')",
+WHERE (`o`.`CustomerID` = 'ALFKI') AND (CAST({MySqlTestHelpers.CastAsDouble("`o`.`OrderID` % 1")} AS char) <> '10')",
//
$@"SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
FROM `Orders` AS `o`
@@ -1699,11 +1699,6 @@ public override async Task Convert_ToString(bool async)
public override Task Datetime_subtraction_TotalDays(bool async)
=> AssertTranslationFailed(() => base.Datetime_subtraction_TotalDays(async));
- private string CastAsDouble(string innerSql)
- => AppConfig.ServerVersion.Supports.DoubleCast
- ? $@"CAST({innerSql} AS double)"
- : $@"(CAST({innerSql} AS decimal(65,30)) + 0e0)";
-
private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
diff --git a/test/EFCore.MySql.FunctionalTests/Query/NorthwindGroupByQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/NorthwindGroupByQueryMySqlTest.cs
index f749ad2d8..637ae56eb 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/NorthwindGroupByQueryMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/NorthwindGroupByQueryMySqlTest.cs
@@ -125,13 +125,7 @@ public override async Task GroupBy_group_Distinct_Select_Distinct_aggregate(bool
await base.GroupBy_group_Distinct_Select_Distinct_aggregate(async);
AssertSql(
- @"SELECT `o`.`CustomerID` AS `Key`, (
- SELECT DISTINCT MAX(DISTINCT (`t`.`OrderDate`))
- FROM (
- SELECT DISTINCT `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`
- FROM `Orders` AS `o0`
- WHERE (`o`.`CustomerID` = `o0`.`CustomerID`) OR (`o`.`CustomerID` IS NULL AND (`o0`.`CustomerID` IS NULL))
- ) AS `t`) AS `Max`
+ @"SELECT `o`.`CustomerID` AS `Key`, MAX(DISTINCT (`o`.`OrderDate`)) AS `Max`
FROM `Orders` AS `o`
GROUP BY `o`.`CustomerID`");
}
diff --git a/test/EFCore.MySql.FunctionalTests/Query/NorthwindIncludeNoTrackingQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/NorthwindIncludeNoTrackingQueryMySqlTest.cs
index 0d6078d69..6472d1d1f 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/NorthwindIncludeNoTrackingQueryMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/NorthwindIncludeNoTrackingQueryMySqlTest.cs
@@ -27,7 +27,7 @@ public override async Task Include_collection_with_last_no_orderby(bool async)
public override Task Include_duplicate_collection_result_operator2(bool async)
{
- // The order of `Orders` can be different, becaues it is not explicitly sorted.
+ // The order of `Orders` can be different, because it is not explicitly sorted.
// The order of the end result can be different as well.
// This is the case on MariaDB.
return AssertQuery(
@@ -59,7 +59,7 @@ public override Task Repro9735(bool async)
public override Task Include_duplicate_collection_result_operator(bool async)
{
- // The order of `Orders` can be different, becaues it is not explicitly sorted.
+ // The order of `Orders` can be different, because it is not explicitly sorted.
// The order of the end result can be different as well.
// This is the case on MariaDB.
return AssertQuery(
diff --git a/test/EFCore.MySql.FunctionalTests/Query/NorthwindIncludeQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/NorthwindIncludeQueryMySqlTest.cs
index af49fb1dd..f7e302dcc 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/NorthwindIncludeQueryMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/NorthwindIncludeQueryMySqlTest.cs
@@ -33,7 +33,7 @@ public override Task Include_duplicate_collection(bool async)
public override Task Include_collection_with_multiple_conditional_order_by(bool async)
{
- // The order of `Orders` can be different, becaues it is not explicitly sorted.
+ // The order of `Orders` can be different, because it is not explicitly sorted.
// This is the case on MariaDB.
return AssertQuery(
async,
@@ -49,7 +49,7 @@ public override Task Include_collection_with_multiple_conditional_order_by(bool
public override Task Include_duplicate_collection_result_operator(bool async)
{
- // The order of `Orders` can be different, becaues it is not explicitly sorted.
+ // The order of `Orders` can be different, because it is not explicitly sorted.
// This is the case on MariaDB.
return AssertQuery(
async,
@@ -67,7 +67,7 @@ from c2 in ss.Set().Include(c => c.Orders).OrderBy(c => c.CustomerID).
public override Task Include_duplicate_collection_result_operator2(bool async)
{
- // The order of `Orders` can be different, becaues it is not explicitly sorted.
+ // The order of `Orders` can be different, because it is not explicitly sorted.
// This is the case on MariaDB.
return AssertQuery(
async,
diff --git a/test/EFCore.MySql.FunctionalTests/Query/NorthwindKeylessEntitiesQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/NorthwindKeylessEntitiesQueryMySqlTest.cs
index 955c46ec4..5df5f6f22 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/NorthwindKeylessEntitiesQueryMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/NorthwindKeylessEntitiesQueryMySqlTest.cs
@@ -1,5 +1,7 @@
-using Microsoft.EntityFrameworkCore.Query;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.TestUtilities;
+using MySqlConnector;
using Xunit;
using Xunit.Abstractions;
@@ -17,8 +19,18 @@ public NorthwindKeylessEntitiesQueryMySqlTest(NorthwindQueryMySqlFixture true;
- [ConditionalFact(Skip = "https://github.com/dotnet/efcore/issues/21627")]
- public override void KeylessEntity_with_nav_defining_query()
- => base.KeylessEntity_with_nav_defining_query();
+ public override async Task KeylessEntity_with_nav_defining_query(bool async)
+ {
+ // FromSql mapping. Issue #21627.
+ await Assert.ThrowsAsync(() => base.KeylessEntity_with_nav_defining_query(async));
+
+ AssertSql(
+ @"SELECT `c`.`CompanyName`, `c`.`OrderCount`, `c`.`SearchTerm`
+FROM `CustomerQueryWithQueryFilter` AS `c`
+WHERE `c`.`OrderCount` > 0");
+ }
+
+ private void AssertSql(params string[] expected)
+ => Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
}
}
diff --git a/test/EFCore.MySql.FunctionalTests/Query/NorthwindMiscellaneousQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/NorthwindMiscellaneousQueryMySqlTest.cs
index 27c9b8b90..2f8859516 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/NorthwindMiscellaneousQueryMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/NorthwindMiscellaneousQueryMySqlTest.cs
@@ -1,6 +1,8 @@
using System;
using System.Linq;
using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.TestModels.Northwind;
@@ -12,6 +14,7 @@
using Pomelo.EntityFrameworkCore.MySql.Tests.TestUtilities.Attributes;
using Xunit;
using Xunit.Abstractions;
+using Xunit.Sdk;
namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests.Query
{
@@ -248,7 +251,7 @@ await AssertQuery(
AssertSql(
@"@__p_0='5'
-SELECT `t`.`OrderID`, `t0`.`ProductID`, `t0`.`OrderID`
+SELECT `t`.`OrderID`, `o0`.`ProductID`, `o0`.`OrderID`
FROM (
SELECT `o`.`OrderID`
FROM `Orders` AS `o`
@@ -256,11 +259,8 @@ await AssertQuery(
ORDER BY `o`.`OrderID`
LIMIT 18446744073709551610 OFFSET @__p_0
) AS `t`
-LEFT JOIN (
- SELECT `o0`.`ProductID`, `o0`.`OrderID`
- FROM `Order Details` AS `o0`
-) AS `t0` ON `t`.`OrderID` = `t0`.`OrderID`
-ORDER BY `t`.`OrderID`, `t0`.`ProductID`");
+LEFT JOIN `Order Details` AS `o0` ON `t`.`OrderID` = `o0`.`OrderID`
+ORDER BY `t`.`OrderID`, `o0`.`ProductID`");
}
///
@@ -289,7 +289,7 @@ await AssertQuery(
@"@__p_1='10'
@__p_0='5'
-SELECT `t`.`OrderID`, `t0`.`ProductID`, `t0`.`OrderID`
+SELECT `t`.`OrderID`, `o0`.`ProductID`, `o0`.`OrderID`
FROM (
SELECT `o`.`OrderID`
FROM `Orders` AS `o`
@@ -297,11 +297,8 @@ await AssertQuery(
ORDER BY `o`.`OrderID`
LIMIT @__p_1 OFFSET @__p_0
) AS `t`
-LEFT JOIN (
- SELECT `o0`.`ProductID`, `o0`.`OrderID`
- FROM `Order Details` AS `o0`
-) AS `t0` ON `t`.`OrderID` = `t0`.`OrderID`
-ORDER BY `t`.`OrderID`, `t0`.`ProductID`");
+LEFT JOIN `Order Details` AS `o0` ON `t`.`OrderID` = `o0`.`OrderID`
+ORDER BY `t`.`OrderID`, `o0`.`ProductID`");
}
///
@@ -328,7 +325,7 @@ await AssertQuery(
AssertSql(
@"@__p_0='10'
-SELECT `t`.`OrderID`, `t0`.`ProductID`, `t0`.`OrderID`
+SELECT `t`.`OrderID`, `o0`.`ProductID`, `o0`.`OrderID`
FROM (
SELECT `o`.`OrderID`
FROM `Orders` AS `o`
@@ -336,11 +333,8 @@ await AssertQuery(
ORDER BY `o`.`OrderID`
LIMIT @__p_0
) AS `t`
-LEFT JOIN (
- SELECT `o0`.`ProductID`, `o0`.`OrderID`
- FROM `Order Details` AS `o0`
-) AS `t0` ON `t`.`OrderID` = `t0`.`OrderID`
-ORDER BY `t`.`OrderID`, `t0`.`ProductID`");
+LEFT JOIN `Order Details` AS `o0` ON `t`.`OrderID` = `o0`.`OrderID`
+ORDER BY `t`.`OrderID`, `o0`.`ProductID`");
}
public override Task Complex_nested_query_doesnt_try_binding_to_grandparent_when_parent_returns_complex_result(bool async)
@@ -359,6 +353,121 @@ public override Task Complex_nested_query_doesnt_try_binding_to_grandparent_when
}
}
+ public override async Task Client_code_using_instance_method_throws(bool async)
+ {
+ Assert.Equal(
+ CoreStrings.ClientProjectionCapturingConstantInMethodInstance(
+ "Pomelo.EntityFrameworkCore.MySql.FunctionalTests.Query.NorthwindMiscellaneousQueryMySqlTest",
+ "InstanceMethod"),
+ (await Assert.ThrowsAsync(
+ () => base.Client_code_using_instance_method_throws(async))).Message);
+
+ AssertSql();
+ }
+
+ public override async Task Client_code_using_instance_in_static_method(bool async)
+ {
+ Assert.Equal(
+ CoreStrings.ClientProjectionCapturingConstantInMethodArgument(
+ "Pomelo.EntityFrameworkCore.MySql.FunctionalTests.Query.NorthwindMiscellaneousQueryMySqlTest",
+ "StaticMethod"),
+ (await Assert.ThrowsAsync(
+ () => base.Client_code_using_instance_in_static_method(async))).Message);
+
+ AssertSql();
+ }
+
+ public override async Task Client_code_using_instance_in_anonymous_type(bool async)
+ {
+ Assert.Equal(
+ CoreStrings.ClientProjectionCapturingConstantInTree(
+ "Pomelo.EntityFrameworkCore.MySql.FunctionalTests.Query.NorthwindMiscellaneousQueryMySqlTest"),
+ (await Assert.ThrowsAsync(
+ () => base.Client_code_using_instance_in_anonymous_type(async))).Message);
+
+ AssertSql();
+ }
+
+ public override async Task Client_code_unknown_method(bool async)
+ {
+ await AssertTranslationFailedWithDetails(
+ () => base.Client_code_unknown_method(async),
+ CoreStrings.QueryUnableToTranslateMethod(
+ "Microsoft.EntityFrameworkCore.Query.NorthwindMiscellaneousQueryTestBase>",
+ nameof(UnknownMethod)));
+
+ AssertSql();
+ }
+
+ public override async Task Entity_equality_through_subquery_composite_key(bool async)
+ {
+ var message = (await Assert.ThrowsAsync(
+ () => base.Entity_equality_through_subquery_composite_key(async))).Message;
+
+ Assert.Equal(
+ CoreStrings.EntityEqualityOnCompositeKeyEntitySubqueryNotSupported("==", nameof(OrderDetail)),
+ message);
+
+ AssertSql();
+ }
+
+ public override async Task Max_on_empty_sequence_throws(bool async)
+ {
+ await Assert.ThrowsAsync(() => base.Max_on_empty_sequence_throws(async));
+
+ AssertSql(
+ @"SELECT (
+ SELECT MAX(`o`.`OrderID`)
+ FROM `Orders` AS `o`
+ WHERE `c`.`CustomerID` = `o`.`CustomerID`) AS `Max`
+FROM `Customers` AS `c`");
+ }
+
+ public override async Task
+ Select_DTO_constructor_distinct_with_collection_projection_translated_to_server_with_binding_after_client_eval(bool async)
+ {
+ using var context = CreateContext();
+ var actualQuery = context.Set()
+ .Where(o => o.OrderID < 10300)
+ .Select(o => new { A = new OrderCountDTO(o.CustomerID), o.CustomerID })
+ .Distinct()
+ .Select(e => new { e.A, Orders = context.Set().Where(o => o.CustomerID == e.CustomerID)
+ .OrderBy(o => o.OrderID) // <-- added
+ .ToList() });
+
+ var actual = async
+ ? (await actualQuery.ToListAsync()).OrderBy(e => e.A.Id).ToList()
+ : actualQuery.ToList().OrderBy(e => e.A.Id).ToList();
+
+ var expected = Fixture.GetExpectedData().Set()
+ .Where(o => o.OrderID < 10300)
+ .Select(o => new { A = new OrderCountDTO(o.CustomerID), o.CustomerID })
+ .Distinct()
+ .Select(e => new { e.A, Orders = Fixture.GetExpectedData().Set().Where(o => o.CustomerID == e.CustomerID)
+ .OrderBy(o => o.OrderID) // <-- added
+ .ToList() })
+ .ToList().OrderBy(e => e.A.Id).ToList();
+
+ Assert.Equal(expected.Count, actual.Count);
+ for (var i = 0; i < expected.Count; i++)
+ {
+ Assert.Equal(expected[i].A.Id, actual[i].A.Id);
+ Assert.True(expected[i].Orders?.SequenceEqual(actual[i].Orders) ?? true);
+ }
+
+ AssertSql(
+"""
+SELECT `t`.`CustomerID`, `o0`.`OrderID`, `o0`.`CustomerID`, `o0`.`EmployeeID`, `o0`.`OrderDate`
+FROM (
+ SELECT DISTINCT `o`.`CustomerID`
+ FROM `Orders` AS `o`
+ WHERE `o`.`OrderID` < 10300
+) AS `t`
+LEFT JOIN `Orders` AS `o0` ON `t`.`CustomerID` = `o0`.`CustomerID`
+ORDER BY `t`.`CustomerID`, `o0`.`OrderID`
+""");
+ }
+
[SupportedServerVersionCondition(nameof(ServerVersionSupport.OuterReferenceInMultiLevelSubquery))]
public override Task DefaultIfEmpty_Sum_over_collection_navigation(bool async)
{
@@ -370,5 +479,37 @@ private void AssertSql(params string[] expected)
protected override void ClearLog()
=> Fixture.TestSqlLoggerFactory.Clear();
+
+ private class OrderCountDTO
+ {
+ public string Id { get; set; }
+ public int Count { get; set; }
+
+ public OrderCountDTO()
+ {
+ }
+
+ public OrderCountDTO(string id)
+ {
+ Id = id;
+ Count = 0;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is null)
+ {
+ return false;
+ }
+
+ return ReferenceEquals(this, obj) ? true : obj.GetType() == GetType() && Equals((OrderCountDTO)obj);
+ }
+
+ private bool Equals(OrderCountDTO other)
+ => string.Equals(Id, other.Id) && Count == other.Count;
+
+ public override int GetHashCode()
+ => HashCode.Combine(Id, Count);
+ }
}
}
diff --git a/test/EFCore.MySql.FunctionalTests/Query/NorthwindSelectQueryMySqlTest.MySql.cs b/test/EFCore.MySql.FunctionalTests/Query/NorthwindSelectQueryMySqlTest.MySql.cs
index 1744d239e..f9a2b1a2a 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/NorthwindSelectQueryMySqlTest.MySql.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/NorthwindSelectQueryMySqlTest.MySql.cs
@@ -42,10 +42,13 @@ await AssertQuery(
AssertSql(
@"SELECT `t`.`Year`, `t`.`Count`
FROM (
- SELECT EXTRACT(year FROM `o`.`OrderDate`) AS `Year`, COUNT(*) AS `Count`, `o`.`CustomerID`, EXTRACT(year FROM `o`.`OrderDate`) = 1995 AS `c`
- FROM `Orders` AS `o`
- WHERE (`o`.`CustomerID` = 'ALFKI') AND `o`.`OrderDate` IS NOT NULL
- GROUP BY `o`.`CustomerID`, EXTRACT(year FROM `o`.`OrderDate`), `c`
+ SELECT `t`.`Year`, COUNT(*) AS `Count`, `t`.`CustomerID`, `t`.`Year` = 1995 AS `c`
+ FROM (
+ SELECT `o`.`CustomerID`, EXTRACT(year FROM `o`.`OrderDate`) AS `Year`
+ FROM `Orders` AS `o`
+ WHERE (`o`.`CustomerID` = 'ALFKI') AND `o`.`OrderDate` IS NOT NULL
+ ) AS `t`
+ GROUP BY `t`.`CustomerID`, `t`.`Year`, `c`
HAVING `c`
) AS `t`
ORDER BY `t`.`Year`");
@@ -73,19 +76,25 @@ await AssertQueryScalar(
assertOrder: true);
AssertSql(
- @"SELECT `t`.`c`
+ @"SELECT `t`.`Year`
FROM (
- SELECT EXTRACT(year FROM `o`.`OrderDate`) AS `c`, `o`.`CustomerID`, EXTRACT(year FROM `o`.`OrderDate`) = 1995 AS `c0`
- FROM `Orders` AS `o`
- WHERE (`o`.`CustomerID` = 'ALFKI') AND `o`.`OrderDate` IS NOT NULL
- GROUP BY `o`.`CustomerID`, EXTRACT(year FROM `o`.`OrderDate`), `c0`
- HAVING `c0`
+ SELECT `t`.`Year`, `t`.`CustomerID`, `t`.`Year` = 1995 AS `c`
+ FROM (
+ SELECT `o`.`CustomerID`, EXTRACT(year FROM `o`.`OrderDate`) AS `Year`
+ FROM `Orders` AS `o`
+ WHERE (`o`.`CustomerID` = 'ALFKI') AND `o`.`OrderDate` IS NOT NULL
+ ) AS `t`
+ GROUP BY `t`.`CustomerID`, `t`.`Year`, `c`
+ HAVING `c`
) AS `t`
UNION ALL
-SELECT EXTRACT(year FROM `o0`.`OrderDate`) AS `c`
-FROM `Orders` AS `o0`
-WHERE `o0`.`OrderDate` IS NOT NULL
-GROUP BY EXTRACT(year FROM `o0`.`OrderDate`)
+SELECT `t1`.`Key` AS `Year`
+FROM (
+ SELECT EXTRACT(year FROM `o0`.`OrderDate`) AS `Key`
+ FROM `Orders` AS `o0`
+ WHERE `o0`.`OrderDate` IS NOT NULL
+) AS `t1`
+GROUP BY `t1`.`Key`
HAVING COUNT(*) > 0");
}
}
diff --git a/test/EFCore.MySql.FunctionalTests/Query/NorthwindSelectQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/NorthwindSelectQueryMySqlTest.cs
index 672447e21..ae81fb104 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/NorthwindSelectQueryMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/NorthwindSelectQueryMySqlTest.cs
@@ -1,4 +1,6 @@
-using System.Threading.Tasks;
+using System;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Pomelo.EntityFrameworkCore.MySql.Infrastructure;
@@ -103,6 +105,28 @@ public override async Task Select_datetime_millisecond_component(bool async)
FROM `Orders` AS `o`");
}
+ public override async Task Correlated_collection_after_distinct_with_complex_projection_not_containing_original_identifier(bool async)
+ {
+ // Identifier set for Distinct. Issue #24440.
+ Assert.Equal(
+ RelationalStrings.InsufficientInformationToIdentifyElementOfCollectionJoin,
+ (await Assert.ThrowsAsync(
+ () => base.Correlated_collection_after_distinct_with_complex_projection_not_containing_original_identifier(async)))
+ .Message);
+
+ AssertSql();
+ }
+
+ public override async Task SelectMany_with_collection_being_correlated_subquery_which_references_non_mapped_properties_from_inner_and_outer_entity(bool async)
+ {
+ await AssertUnableToTranslateEFProperty(
+ () => base
+ .SelectMany_with_collection_being_correlated_subquery_which_references_non_mapped_properties_from_inner_and_outer_entity(
+ async));
+
+ AssertSql();
+ }
+
[ConditionalTheory(Skip = "issue #573")]
public override Task Project_single_element_from_collection_with_OrderBy_Take_and_FirstOrDefault(bool async)
{
diff --git a/test/EFCore.MySql.FunctionalTests/Query/NorthwindSetOperationsQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/NorthwindSetOperationsQueryMySqlTest.cs
index 43a6df327..a20bc48b5 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/NorthwindSetOperationsQueryMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/NorthwindSetOperationsQueryMySqlTest.cs
@@ -1,9 +1,9 @@
-using System.Threading.Tasks;
-using Microsoft.EntityFrameworkCore;
+using System;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Pomelo.EntityFrameworkCore.MySql.Infrastructure;
-using Pomelo.EntityFrameworkCore.MySql.Storage;
using Pomelo.EntityFrameworkCore.MySql.Tests.TestUtilities.Attributes;
using Xunit;
using Xunit.Abstractions;
@@ -25,6 +25,17 @@ public NorthwindSetOperationsQueryMySqlTest(
protected override bool CanExecuteQueryString
=> true;
+ public override async Task Client_eval_Union_FirstOrDefault(bool async)
+ {
+ // Client evaluation in projection. Issue #16243.
+ Assert.Equal(
+ RelationalStrings.SetOperationsNotAllowedAfterClientEvaluation,
+ (await Assert.ThrowsAsync(
+ () => base.Client_eval_Union_FirstOrDefault(async))).Message);
+
+ AssertSql();
+ }
+
[SupportedServerVersionCondition(nameof(ServerVersionSupport.ExceptIntercept))]
public override async Task Intersect(bool async)
{
diff --git a/test/EFCore.MySql.FunctionalTests/Query/NorthwindSplitIncludeNoTrackingQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/NorthwindSplitIncludeNoTrackingQueryMySqlTest.cs
index 0bf3160d1..2e6fe75d5 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/NorthwindSplitIncludeNoTrackingQueryMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/NorthwindSplitIncludeNoTrackingQueryMySqlTest.cs
@@ -19,7 +19,7 @@ public NorthwindSplitIncludeNoTrackingQueryMySqlTest(NorthwindQueryMySqlFixture<
public override Task Include_duplicate_collection_result_operator2(bool async)
{
- // The order of `Orders` can be different, becaues it is not explicitly sorted.
+ // The order of `Orders` can be different, because it is not explicitly sorted.
// The order of the end result can be different as well.
// This is the case on MariaDB.
return AssertQuery(
diff --git a/test/EFCore.MySql.FunctionalTests/Query/NorthwindSplitIncludeQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/NorthwindSplitIncludeQueryMySqlTest.cs
index b899b46e6..8dddf1814 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/NorthwindSplitIncludeQueryMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/NorthwindSplitIncludeQueryMySqlTest.cs
@@ -22,7 +22,7 @@ public NorthwindSplitIncludeQueryMySqlTest(NorthwindQueryMySqlFixture().Include(o => o.OrderDetails) select o)
public override Task Include_duplicate_collection_result_operator(bool async)
{
- // The order of `Orders` can be different, becaues it is not explicitly sorted.
+ // The order of `Orders` can be different, because it is not explicitly sorted.
// This is the case on MariaDB.
return AssertQuery(
async,
@@ -113,7 +113,7 @@ from c2 in ss.Set().Include(c => c.Orders).OrderBy(c => c.CustomerID).
public override Task Include_duplicate_collection_result_operator2(bool async)
{
- // The order of `Orders` can be different, becaues it is not explicitly sorted.
+ // The order of `Orders` can be different, because it is not explicitly sorted.
// The order of the end result can be different as well.
// This is the case on MariaDB.
return AssertQuery(
diff --git a/test/EFCore.MySql.FunctionalTests/Query/NorthwindSqlQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/NorthwindSqlQueryMySqlTest.cs
new file mode 100644
index 000000000..2d76c98dd
--- /dev/null
+++ b/test/EFCore.MySql.FunctionalTests/Query/NorthwindSqlQueryMySqlTest.cs
@@ -0,0 +1,81 @@
+using System.Data.Common;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore.Query;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+using MySqlConnector;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests.Query;
+
+public class NorthwindSqlQueryMySqlTest : NorthwindSqlQueryTestBase>
+{
+ public NorthwindSqlQueryMySqlTest(NorthwindQueryMySqlFixture fixture, ITestOutputHelper testOutputHelper)
+ : base(fixture)
+ {
+ Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
+ }
+
+ [ConditionalFact]
+ public virtual void Check_all_tests_overridden()
+ => TestHelpers.AssertAllMethodsOverridden(GetType());
+
+ public override async Task SqlQueryRaw_over_int(bool async)
+ {
+ await base.SqlQueryRaw_over_int(async);
+
+ AssertSql(
+"""
+SELECT `ProductID` FROM `Products`
+""");
+ }
+
+ public override async Task SqlQuery_composed_Contains(bool async)
+ {
+ await base.SqlQuery_composed_Contains(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`
+FROM `Orders` AS `o`
+WHERE EXISTS (
+ SELECT 1
+ FROM (
+ SELECT `ProductID` AS `Value` FROM `Products`
+ ) AS `t`
+ WHERE CAST(`t`.`Value` AS signed) = `o`.`OrderID`)
+""");
+ }
+
+ public override async Task SqlQuery_composed_Join(bool async)
+ {
+ await base.SqlQuery_composed_Join(async);
+
+ AssertSql(
+"""
+SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate`, CAST(`t`.`Value` AS signed) AS `p`
+FROM `Orders` AS `o`
+INNER JOIN (
+ SELECT `ProductID` AS `Value` FROM `Products`
+) AS `t` ON `o`.`OrderID` = CAST(`t`.`Value` AS signed)
+""");
+ }
+
+ public override async Task SqlQuery_over_int_with_parameter(bool async)
+ {
+ await base.SqlQuery_over_int_with_parameter(async);
+
+ AssertSql(
+"""
+p0='10'
+
+SELECT `ProductID` FROM `Products` WHERE `ProductID` = @p0
+""");
+ }
+
+ protected override DbParameter CreateDbParameter(string name, object value)
+ => new MySqlParameter { ParameterName = name, Value = value };
+
+ private void AssertSql(params string[] expected)
+ => Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
+}
diff --git a/test/EFCore.MySql.FunctionalTests/Query/NorthwindStringIncludeQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/NorthwindStringIncludeQueryMySqlTest.cs
index 18bc0604a..326270bcf 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/NorthwindStringIncludeQueryMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/NorthwindStringIncludeQueryMySqlTest.cs
@@ -27,7 +27,7 @@ public override async Task Include_collection_with_last_no_orderby(bool async)
public override Task Include_collection_with_multiple_conditional_order_by(bool async)
{
- // The order of `Orders` can be different, becaues it is not explicitly sorted.
+ // The order of `Orders` can be different, because it is not explicitly sorted.
// This is the case on MariaDB.
return AssertQuery(
async,
@@ -43,7 +43,7 @@ public override Task Include_collection_with_multiple_conditional_order_by(bool
public override Task Include_duplicate_collection_result_operator(bool async)
{
- // The order of `Orders` can be different, becaues it is not explicitly sorted.
+ // The order of `Orders` can be different, because it is not explicitly sorted.
// This is the case on MariaDB.
return AssertQuery(
async,
@@ -61,7 +61,7 @@ from c2 in ss.Set().Include(c => c.Orders).OrderBy(c => c.CustomerID).
public override Task Include_duplicate_collection_result_operator2(bool async)
{
- // The order of `Orders` can be different, becaues it is not explicitly sorted.
+ // The order of `Orders` can be different, because it is not explicitly sorted.
// The order of the end result can be different as well.
// This is the case on MariaDB.
return AssertQuery(
diff --git a/test/EFCore.MySql.FunctionalTests/Query/NorthwindWhereQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/NorthwindWhereQueryMySqlTest.cs
index 2afe7a27a..a0992358e 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/NorthwindWhereQueryMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/NorthwindWhereQueryMySqlTest.cs
@@ -570,6 +570,86 @@ await AssertQuery(
WHERE @__Concat_0 = `c`.`CompanyName`");
}
+ public override async Task Where_bitwise_xor(bool async)
+ {
+ // Cannot eval 'where (([c].CustomerID == \"ALFKI\") ^ True)'. Issue #16645.
+ await AssertTranslationFailed(() => base.Where_bitwise_xor(async));
+
+ AssertSql();
+ }
+
+ public override async Task Where_compare_constructed_equal(bool async)
+ {
+ // Anonymous type to constant comparison. Issue #14672.
+ await AssertTranslationFailed(() => base.Where_compare_constructed_equal(async));
+
+ AssertSql();
+ }
+
+ public override async Task Where_compare_constructed_multi_value_equal(bool async)
+ {
+ // Anonymous type to constant comparison. Issue #14672.
+ await AssertTranslationFailed(() => base.Where_compare_constructed_multi_value_equal(async));
+
+ AssertSql();
+ }
+
+ public override async Task Where_compare_constructed_multi_value_not_equal(bool async)
+ {
+ // Anonymous type to constant comparison. Issue #14672.
+ await AssertTranslationFailed(() => base.Where_compare_constructed_multi_value_not_equal(async));
+
+ AssertSql();
+ }
+
+ public override async Task Where_compare_tuple_create_constructed_equal(bool async)
+ {
+ // Anonymous type to constant comparison. Issue #14672.
+ await AssertTranslationFailed(() => base.Where_compare_tuple_create_constructed_equal(async));
+
+ AssertSql();
+ }
+
+ public override async Task Where_compare_tuple_create_constructed_multi_value_equal(bool async)
+ {
+ // Anonymous type to constant comparison. Issue #14672.
+ await AssertTranslationFailed(() => base.Where_compare_tuple_create_constructed_multi_value_equal(async));
+
+ AssertSql();
+ }
+
+ public override async Task Where_compare_tuple_create_constructed_multi_value_not_equal(bool async)
+ {
+ // Anonymous type to constant comparison. Issue #14672.
+ await AssertTranslationFailed(() => base.Where_compare_tuple_create_constructed_multi_value_not_equal(async));
+
+ AssertSql();
+ }
+
+ public override async Task Where_compare_tuple_constructed_equal(bool async)
+ {
+ // Anonymous type to constant comparison. Issue #14672.
+ await AssertTranslationFailed(() => base.Where_compare_tuple_constructed_equal(async));
+
+ AssertSql();
+ }
+
+ public override async Task Where_compare_tuple_constructed_multi_value_equal(bool async)
+ {
+ // Anonymous type to constant comparison. Issue #14672.
+ await AssertTranslationFailed(() => base.Where_compare_tuple_constructed_multi_value_equal(async));
+
+ AssertSql();
+ }
+
+ public override async Task Where_compare_tuple_constructed_multi_value_not_equal(bool async)
+ {
+ // Anonymous type to constant comparison. Issue #14672.
+ await AssertTranslationFailed(() => base.Where_compare_tuple_constructed_multi_value_not_equal(async));
+
+ AssertSql();
+ }
+
private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
diff --git a/test/EFCore.MySql.FunctionalTests/Query/QueryNavigationsMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/QueryNavigationsMySqlTest.cs
index b0364acff..f160d6714 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/QueryNavigationsMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/QueryNavigationsMySqlTest.cs
@@ -1,6 +1,8 @@
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.TestUtilities;
+using Pomelo.EntityFrameworkCore.MySql.Infrastructure;
+using Pomelo.EntityFrameworkCore.MySql.Tests.TestUtilities.Attributes;
using Xunit;
using Xunit.Abstractions;
@@ -33,10 +35,10 @@ public override Task Where_subquery_on_navigation2(bool async)
return base.Where_subquery_on_navigation2(async);
}
- [ConditionalFact(Skip = "Issue #573")]
- public override void Navigation_in_subquery_referencing_outer_query_with_client_side_result_operator_and_count()
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.OuterReferenceInMultiLevelSubquery))]
+ public override Task Navigation_in_subquery_referencing_outer_query_with_client_side_result_operator_and_count(bool async)
{
- base.Navigation_in_subquery_referencing_outer_query_with_client_side_result_operator_and_count();
+ return base.Navigation_in_subquery_referencing_outer_query_with_client_side_result_operator_and_count(async);
}
private void AssertSql(params string[] expected)
diff --git a/test/EFCore.MySql.FunctionalTests/Query/SimpleQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/SimpleQueryMySqlTest.cs
index ece993fc2..5c8f5b440 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/SimpleQueryMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/SimpleQueryMySqlTest.cs
@@ -4,6 +4,8 @@
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Pomelo.EntityFrameworkCore.MySql.FunctionalTests.TestUtilities;
+using Pomelo.EntityFrameworkCore.MySql.Infrastructure;
+using Pomelo.EntityFrameworkCore.MySql.Tests.TestUtilities.Attributes;
// ReSharper disable InconsistentNaming
namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests.Query
@@ -41,5 +43,17 @@ public override async Task Multiple_different_entity_type_from_different_namespa
using var context = contextFactory.CreateContext();
var bad = context.Set().FromSqlRaw(@"SELECT cast(null as signed) AS MyValue").ToList(); // <-- MySQL uses `signed` instead of `int` in CAST() expressions
}
+
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.OuterReferenceInMultiLevelSubquery))]
+ public override Task Group_by_multiple_aggregate_joining_different_tables(bool async)
+ {
+ return base.Group_by_multiple_aggregate_joining_different_tables(async);
+ }
+
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.OuterReferenceInMultiLevelSubquery))]
+ public override Task Group_by_multiple_aggregate_joining_different_tables_with_query_filter(bool async)
+ {
+ return base.Group_by_multiple_aggregate_joining_different_tables_with_query_filter(async);
+ }
}
}
diff --git a/test/EFCore.MySql.FunctionalTests/Query/SpatialGeographyQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/SpatialGeographyQueryMySqlTest.cs
index 7de267403..cbce16518 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/SpatialGeographyQueryMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/SpatialGeographyQueryMySqlTest.cs
@@ -16,6 +16,7 @@
namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests.Query
{
+ // TODO: Inherit from SpatialQueryRelationalTestBase.
public class SpatialGeographyQueryMySqlTest : QueryTestBase
{
public SpatialGeographyQueryMySqlTest(SpatialGeographyQueryMySqlFixture fixture, ITestOutputHelper testOutputHelper)
@@ -200,6 +201,7 @@ public virtual IQueryable Set()
}
}
+ // TODO: Inherit from SpatialQueryRelationalFixture.
public class SpatialGeographyQueryMySqlFixture : SharedStoreFixtureBase, IQueryFixtureBase
{
private GeometryFactory _geometryFactory;
@@ -256,14 +258,14 @@ public ISetSource GetExpectedData()
=> new SpatialGeographyData(_geometryFactory);
// CHECK: Unused?
- public IReadOnlyDictionary GetEntitySorters()
+ public IReadOnlyDictionary EntitySorters
=> new Dictionary>
{
{ typeof(SpatialGeographyContext.City), e => ((SpatialGeographyContext.City)e)?.CityId },
}.ToDictionary(e => e.Key, e => (object)e.Value);
// CHECK: Unused?
- public IReadOnlyDictionary GetEntityAsserters()
+ public IReadOnlyDictionary EntityAsserters
=> new Dictionary>
{
{
diff --git a/test/EFCore.MySql.FunctionalTests/Query/SpatialQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/SpatialQueryMySqlTest.cs
index 338ebfc77..530286547 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/SpatialQueryMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/SpatialQueryMySqlTest.cs
@@ -1,10 +1,10 @@
using System.Linq;
using System.Threading.Tasks;
-using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.TestModels.SpatialModel;
+using NetTopologySuite.Geometries;
+using NetTopologySuite.Geometries.Utilities;
using Pomelo.EntityFrameworkCore.MySql.Infrastructure;
-using Pomelo.EntityFrameworkCore.MySql.Storage;
using Pomelo.EntityFrameworkCore.MySql.Tests.TestUtilities.Attributes;
using Xunit;
using Xunit.Abstractions;
@@ -56,6 +56,28 @@ public override Task Relate(bool async)
public override Task ConvexHull(bool async)
=> base.ConvexHull(async);
+ public override Task Combine_aggregate(bool async)
+ => AssertQuery(
+ async,
+ ss => ss.Set()
+ .Where(e => e.Point != null)
+ .GroupBy(e => e.Group)
+ .Select(g => new { Id = g.Key, Combined = GeometryCombiner.Combine(g.Select(e => e.Point)
+ .OrderBy(p => p.X).ThenBy(p => p.Y)) }), // <-- needs to be explicitly ordered to be deterministic
+ elementSorter: x => x.Id,
+ elementAsserter: (e, a) =>
+ {
+ Assert.Equal(e.Id, a.Id);
+
+ // Note that NTS returns a MultiPoint (which is a subclass of GeometryCollection), whereas SQL Server returns a
+ // GeometryCollection.
+ var eCollection = (GeometryCollection)e.Combined;
+ var aCollection = (GeometryCollection)a.Combined;
+
+ Assert.Equal(eCollection.Geometries, aCollection.Geometries);
+ });
+
+
#region Not supported by MySQL and MariaDB
public override Task Buffer_quadrantSegments(bool async) => Task.CompletedTask;
diff --git a/test/EFCore.MySql.FunctionalTests/Query/SqlExecutorMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/SqlExecutorMySqlTest.cs
index d92e846c0..1f145220b 100644
--- a/test/EFCore.MySql.FunctionalTests/Query/SqlExecutorMySqlTest.cs
+++ b/test/EFCore.MySql.FunctionalTests/Query/SqlExecutorMySqlTest.cs
@@ -15,7 +15,91 @@ public SqlExecutorMySqlTest(NorthwindQueryMySqlFixture fixt
{
}
- [ConditionalFact]
+ protected virtual int DefaultStoredProcedureResult
+ => 0;
+
+ protected virtual int DefaultSqlResult
+ => -1;
+
+ public override void Executes_stored_procedure()
+ {
+ using var context = CreateContext();
+ Assert.Equal(DefaultStoredProcedureResult, context.Database.ExecuteSqlRaw(TenMostExpensiveProductsSproc));
+ }
+
+ public override void Executes_stored_procedure_with_parameter()
+ {
+ using var context = CreateContext();
+ var parameter = CreateDbParameter("@CustomerID", "ALFKI");
+
+ Assert.Equal(DefaultStoredProcedureResult, context.Database.ExecuteSqlRaw(CustomerOrderHistorySproc, parameter));
+ }
+
+ public override void Executes_stored_procedure_with_generated_parameter()
+ {
+ using var context = CreateContext();
+ Assert.Equal(DefaultStoredProcedureResult, context.Database.ExecuteSqlRaw(CustomerOrderHistoryWithGeneratedParameterSproc, "ALFKI"));
+ }
+
+ public override void Query_with_parameters_interpolated_2()
+ {
+ var city = "London";
+ var contactTitle = "Sales Representative";
+
+ using var context = CreateContext();
+ var actual = context.Database
+ .ExecuteSql(
+ $@"SELECT COUNT(*) FROM `Customers` WHERE `City` = {city} AND `ContactTitle` = {contactTitle}");
+
+ Assert.Equal(DefaultSqlResult, actual);
+ }
+
+ public override void Query_with_DbParameters_interpolated_2()
+ {
+ var city = CreateDbParameter("city", "London");
+ var contactTitle = CreateDbParameter("contactTitle", "Sales Representative");
+
+ using var context = CreateContext();
+ var actual = context.Database
+ .ExecuteSql(
+ $@"SELECT COUNT(*) FROM `Customers` WHERE `City` = {city} AND `ContactTitle` = {contactTitle}");
+
+ Assert.Equal(DefaultSqlResult, actual);
+ }
+
+ public override async Task Executes_stored_procedure_async()
+ {
+ using var context = CreateContext();
+ Assert.Equal(DefaultStoredProcedureResult, await context.Database.ExecuteSqlRawAsync(TenMostExpensiveProductsSproc));
+ }
+
+ public override async Task Executes_stored_procedure_with_parameter_async()
+ {
+ using var context = CreateContext();
+ var parameter = CreateDbParameter("@CustomerID", "ALFKI");
+
+ Assert.Equal(DefaultStoredProcedureResult, await context.Database.ExecuteSqlRawAsync(CustomerOrderHistorySproc, parameter));
+ }
+
+ public override async Task Executes_stored_procedure_with_generated_parameter_async()
+ {
+ using var context = CreateContext();
+ Assert.Equal(DefaultStoredProcedureResult, await context.Database.ExecuteSqlRawAsync(CustomerOrderHistoryWithGeneratedParameterSproc, "ALFKI"));
+ }
+
+ public override async Task Query_with_parameters_interpolated_async_2()
+ {
+ var city = "London";
+ var contactTitle = "Sales Representative";
+
+ using var context = CreateContext();
+ var actual = await context.Database
+ .ExecuteSqlAsync(
+ $@"SELECT COUNT(*) FROM `Customers` WHERE `City` = {city} AND `ContactTitle` = {contactTitle}");
+
+ Assert.Equal(DefaultSqlResult, actual);
+ }
+
public override void Query_with_parameters()
{
var city = "London";
@@ -27,11 +111,10 @@ public override void Query_with_parameters()
.ExecuteSqlRaw(
@"SELECT COUNT(*) FROM `Customers` WHERE `City` = {0} AND `ContactTitle` = {1}", city, contactTitle);
- Assert.Equal(-1, actual);
+ Assert.Equal(DefaultSqlResult, actual);
}
}
- [Fact]
public override void Query_with_dbParameter_with_name()
{
var city = CreateDbParameter("@city", "London");
@@ -42,11 +125,10 @@ public override void Query_with_dbParameter_with_name()
.ExecuteSqlRaw(
@"SELECT COUNT(*) FROM `Customers` WHERE `City` = @city", city);
- Assert.Equal(-1, actual);
+ Assert.Equal(DefaultSqlResult, actual);
}
}
- [Fact]
public override void Query_with_positional_dbParameter_with_name()
{
var city = CreateDbParameter("@city", "London");
@@ -57,11 +139,10 @@ public override void Query_with_positional_dbParameter_with_name()
.ExecuteSqlRaw(
@"SELECT COUNT(*) FROM `Customers` WHERE `City` = {0}", city);
- Assert.Equal(-1, actual);
+ Assert.Equal(DefaultSqlResult, actual);
}
}
- [Fact]
public override void Query_with_positional_dbParameter_without_name()
{
var city = CreateDbParameter(name: null, value: "London");
@@ -72,11 +153,10 @@ public override void Query_with_positional_dbParameter_without_name()
.ExecuteSqlRaw(
@"SELECT COUNT(*) FROM `Customers` WHERE `City` = {0}", city);
- Assert.Equal(-1, actual);
+ Assert.Equal(DefaultSqlResult, actual);
}
}
- [Fact]
public override void Query_with_dbParameters_mixed()
{
var city = "London";
@@ -91,17 +171,16 @@ public override void Query_with_dbParameters_mixed()
.ExecuteSqlRaw(
@"SELECT COUNT(*) FROM `Customers` WHERE `City` = {0} AND `ContactTitle` = @contactTitle", city, contactTitleParameter);
- Assert.Equal(-1, actual);
+ Assert.Equal(DefaultSqlResult, actual);
actual = context.Database
.ExecuteSqlRaw(
@"SELECT COUNT(*) FROM `Customers` WHERE `City` = @city AND `ContactTitle` = {1}", cityParameter, contactTitle);
- Assert.Equal(-1, actual);
+ Assert.Equal(DefaultSqlResult, actual);
}
}
- [Fact]
public override void Query_with_parameters_interpolated()
{
var city = "London";
@@ -113,11 +192,10 @@ public override void Query_with_parameters_interpolated()
.ExecuteSqlInterpolated(
$@"SELECT COUNT(*) FROM `Customers` WHERE `City` = {city} AND `ContactTitle` = {contactTitle}");
- Assert.Equal(-1, actual);
+ Assert.Equal(DefaultSqlResult, actual);
}
}
- [ConditionalFact]
public override async Task Query_with_parameters_async()
{
var city = "London";
@@ -129,11 +207,10 @@ public override async Task Query_with_parameters_async()
.ExecuteSqlRawAsync(
@"SELECT COUNT(*) FROM `Customers` WHERE `City` = {0} AND `ContactTitle` = {1}", city, contactTitle);
- Assert.Equal(-1, actual);
+ Assert.Equal(DefaultSqlResult, actual);
}
}
- [Fact]
public override async Task Query_with_parameters_interpolated_async()
{
var city = "London";
@@ -145,7 +222,7 @@ public override async Task Query_with_parameters_interpolated_async()
.ExecuteSqlInterpolatedAsync(
$@"SELECT COUNT(*) FROM `Customers` WHERE `City` = {city} AND `ContactTitle` = {contactTitle}");
- Assert.Equal(-1, actual);
+ Assert.Equal(DefaultSqlResult, actual);
}
}
@@ -159,7 +236,7 @@ public override void Query_with_DbParameters_interpolated()
.ExecuteSqlInterpolated(
$@"SELECT COUNT(*) FROM `Customers` WHERE `City` = {city} AND `ContactTitle` = {contactTitle}");
- Assert.Equal(-1, actual);
+ Assert.Equal(DefaultSqlResult, actual);
}
protected override DbParameter CreateDbParameter(string name, object value)
diff --git a/test/EFCore.MySql.FunctionalTests/Query/TPCFiltersInheritanceQueryMySqlFixture.cs b/test/EFCore.MySql.FunctionalTests/Query/TPCFiltersInheritanceQueryMySqlFixture.cs
new file mode 100644
index 000000000..d8a26b14a
--- /dev/null
+++ b/test/EFCore.MySql.FunctionalTests/Query/TPCFiltersInheritanceQueryMySqlFixture.cs
@@ -0,0 +1,7 @@
+namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests.Query;
+
+public class TPCFiltersInheritanceQueryMySqlFixture : TPCInheritanceQueryMySqlFixture
+{
+ protected override bool EnableFilters
+ => true;
+}
diff --git a/test/EFCore.MySql.FunctionalTests/Query/TPCFiltersInheritanceQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/TPCFiltersInheritanceQueryMySqlTest.cs
new file mode 100644
index 000000000..2f2aa5f92
--- /dev/null
+++ b/test/EFCore.MySql.FunctionalTests/Query/TPCFiltersInheritanceQueryMySqlTest.cs
@@ -0,0 +1,220 @@
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore.Query;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests.Query;
+
+public class TPCFiltersInheritanceQueryMySqlTest : TPCFiltersInheritanceQueryTestBase
+{
+ public TPCFiltersInheritanceQueryMySqlTest(
+ TPCFiltersInheritanceQueryMySqlFixture fixture,
+ ITestOutputHelper testOutputHelper)
+ : base(fixture)
+ {
+ Fixture.TestSqlLoggerFactory.Clear();
+ //Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
+ }
+
+ [ConditionalFact]
+ public virtual void Check_all_tests_overridden()
+ => TestHelpers.AssertAllMethodsOverridden(GetType());
+
+ public override async Task Can_use_of_type_animal(bool async)
+ {
+ await base.Can_use_of_type_animal(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`CountryId`, `t`.`Name`, `t`.`Species`, `t`.`EagleId`, `t`.`IsFlightless`, `t`.`Group`, `t`.`FoundOn`, `t`.`Discriminator`
+FROM (
+ SELECT `e`.`Id`, `e`.`CountryId`, `e`.`Name`, `e`.`Species`, `e`.`EagleId`, `e`.`IsFlightless`, `e`.`Group`, NULL AS `FoundOn`, 'Eagle' AS `Discriminator`
+ FROM `Eagle` AS `e`
+ UNION ALL
+ SELECT `k`.`Id`, `k`.`CountryId`, `k`.`Name`, `k`.`Species`, `k`.`EagleId`, `k`.`IsFlightless`, NULL AS `Group`, `k`.`FoundOn`, 'Kiwi' AS `Discriminator`
+ FROM `Kiwi` AS `k`
+) AS `t`
+WHERE `t`.`CountryId` = 1
+ORDER BY `t`.`Species`
+""");
+ }
+
+ public override async Task Can_use_is_kiwi(bool async)
+ {
+ await base.Can_use_is_kiwi(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`CountryId`, `t`.`Name`, `t`.`Species`, `t`.`EagleId`, `t`.`IsFlightless`, `t`.`Group`, `t`.`FoundOn`, `t`.`Discriminator`
+FROM (
+ SELECT `k`.`Id`, `k`.`CountryId`, `k`.`Name`, `k`.`Species`, `k`.`EagleId`, `k`.`IsFlightless`, NULL AS `Group`, `k`.`FoundOn`, 'Kiwi' AS `Discriminator`
+ FROM `Kiwi` AS `k`
+) AS `t`
+WHERE `t`.`CountryId` = 1
+""");
+ }
+
+ public override async Task Can_use_is_kiwi_with_other_predicate(bool async)
+ {
+ await base.Can_use_is_kiwi_with_other_predicate(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`CountryId`, `t`.`Name`, `t`.`Species`, `t`.`EagleId`, `t`.`IsFlightless`, `t`.`Group`, `t`.`FoundOn`, `t`.`Discriminator`
+FROM (
+ SELECT `e`.`Id`, `e`.`CountryId`, `e`.`Name`, `e`.`Species`, `e`.`EagleId`, `e`.`IsFlightless`, `e`.`Group`, NULL AS `FoundOn`, 'Eagle' AS `Discriminator`
+ FROM `Eagle` AS `e`
+ UNION ALL
+ SELECT `k`.`Id`, `k`.`CountryId`, `k`.`Name`, `k`.`Species`, `k`.`EagleId`, `k`.`IsFlightless`, NULL AS `Group`, `k`.`FoundOn`, 'Kiwi' AS `Discriminator`
+ FROM `Kiwi` AS `k`
+) AS `t`
+WHERE (`t`.`CountryId` = 1) AND ((`t`.`Discriminator` = 'Kiwi') AND (`t`.`CountryId` = 1))
+""");
+ }
+
+ public override async Task Can_use_is_kiwi_in_projection(bool async)
+ {
+ await base.Can_use_is_kiwi_in_projection(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Discriminator` = 'Kiwi'
+FROM (
+ SELECT `e`.`CountryId`, 'Eagle' AS `Discriminator`
+ FROM `Eagle` AS `e`
+ UNION ALL
+ SELECT `k`.`CountryId`, 'Kiwi' AS `Discriminator`
+ FROM `Kiwi` AS `k`
+) AS `t`
+WHERE `t`.`CountryId` = 1
+""");
+ }
+
+ public override async Task Can_use_of_type_bird(bool async)
+ {
+ await base.Can_use_of_type_bird(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`CountryId`, `t`.`Name`, `t`.`Species`, `t`.`EagleId`, `t`.`IsFlightless`, `t`.`Group`, `t`.`FoundOn`, `t`.`Discriminator`
+FROM (
+ SELECT `e`.`Id`, `e`.`CountryId`, `e`.`Name`, `e`.`Species`, `e`.`EagleId`, `e`.`IsFlightless`, `e`.`Group`, NULL AS `FoundOn`, 'Eagle' AS `Discriminator`
+ FROM `Eagle` AS `e`
+ UNION ALL
+ SELECT `k`.`Id`, `k`.`CountryId`, `k`.`Name`, `k`.`Species`, `k`.`EagleId`, `k`.`IsFlightless`, NULL AS `Group`, `k`.`FoundOn`, 'Kiwi' AS `Discriminator`
+ FROM `Kiwi` AS `k`
+) AS `t`
+WHERE `t`.`CountryId` = 1
+ORDER BY `t`.`Species`
+""");
+ }
+
+ public override async Task Can_use_of_type_bird_predicate(bool async)
+ {
+ await base.Can_use_of_type_bird_predicate(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`CountryId`, `t`.`Name`, `t`.`Species`, `t`.`EagleId`, `t`.`IsFlightless`, `t`.`Group`, `t`.`FoundOn`, `t`.`Discriminator`
+FROM (
+ SELECT `e`.`Id`, `e`.`CountryId`, `e`.`Name`, `e`.`Species`, `e`.`EagleId`, `e`.`IsFlightless`, `e`.`Group`, NULL AS `FoundOn`, 'Eagle' AS `Discriminator`
+ FROM `Eagle` AS `e`
+ UNION ALL
+ SELECT `k`.`Id`, `k`.`CountryId`, `k`.`Name`, `k`.`Species`, `k`.`EagleId`, `k`.`IsFlightless`, NULL AS `Group`, `k`.`FoundOn`, 'Kiwi' AS `Discriminator`
+ FROM `Kiwi` AS `k`
+) AS `t`
+WHERE (`t`.`CountryId` = 1) AND (`t`.`CountryId` = 1)
+ORDER BY `t`.`Species`
+""");
+ }
+
+ public override async Task Can_use_of_type_bird_with_projection(bool async)
+ {
+ await base.Can_use_of_type_bird_with_projection(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Name`
+FROM (
+ SELECT `e`.`CountryId`, `e`.`Name`
+ FROM `Eagle` AS `e`
+ UNION ALL
+ SELECT `k`.`CountryId`, `k`.`Name`
+ FROM `Kiwi` AS `k`
+) AS `t`
+WHERE `t`.`CountryId` = 1
+""");
+ }
+
+ public override async Task Can_use_of_type_bird_first(bool async)
+ {
+ await base.Can_use_of_type_bird_first(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`CountryId`, `t`.`Name`, `t`.`Species`, `t`.`EagleId`, `t`.`IsFlightless`, `t`.`Group`, `t`.`FoundOn`, `t`.`Discriminator`
+FROM (
+ SELECT `e`.`Id`, `e`.`CountryId`, `e`.`Name`, `e`.`Species`, `e`.`EagleId`, `e`.`IsFlightless`, `e`.`Group`, NULL AS `FoundOn`, 'Eagle' AS `Discriminator`
+ FROM `Eagle` AS `e`
+ UNION ALL
+ SELECT `k`.`Id`, `k`.`CountryId`, `k`.`Name`, `k`.`Species`, `k`.`EagleId`, `k`.`IsFlightless`, NULL AS `Group`, `k`.`FoundOn`, 'Kiwi' AS `Discriminator`
+ FROM `Kiwi` AS `k`
+) AS `t`
+WHERE `t`.`CountryId` = 1
+ORDER BY `t`.`Species`
+LIMIT 1
+""");
+ }
+
+ public override async Task Can_use_of_type_kiwi(bool async)
+ {
+ await base.Can_use_of_type_kiwi(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`CountryId`, `t`.`Name`, `t`.`Species`, `t`.`EagleId`, `t`.`IsFlightless`, `t`.`FoundOn`, `t`.`Discriminator`
+FROM (
+ SELECT `k`.`Id`, `k`.`CountryId`, `k`.`Name`, `k`.`Species`, `k`.`EagleId`, `k`.`IsFlightless`, `k`.`FoundOn`, 'Kiwi' AS `Discriminator`
+ FROM `Kiwi` AS `k`
+) AS `t`
+WHERE `t`.`CountryId` = 1
+""");
+ }
+
+ public override async Task Can_use_derived_set(bool async)
+ {
+ await base.Can_use_derived_set(async);
+
+ AssertSql(
+"""
+SELECT `e`.`Id`, `e`.`CountryId`, `e`.`Name`, `e`.`Species`, `e`.`EagleId`, `e`.`IsFlightless`, `e`.`Group`
+FROM `Eagle` AS `e`
+WHERE `e`.`CountryId` = 1
+""");
+ }
+
+ public override async Task Can_use_IgnoreQueryFilters_and_GetDatabaseValues(bool async)
+ {
+ await base.Can_use_IgnoreQueryFilters_and_GetDatabaseValues(async);
+
+ AssertSql(
+"""
+SELECT `e`.`Id`, `e`.`CountryId`, `e`.`Name`, `e`.`Species`, `e`.`EagleId`, `e`.`IsFlightless`, `e`.`Group`
+FROM `Eagle` AS `e`
+LIMIT 2
+""",
+ //
+ """
+@__p_0='2'
+
+SELECT `e`.`Id`, `e`.`CountryId`, `e`.`Name`, `e`.`Species`, `e`.`EagleId`, `e`.`IsFlightless`, `e`.`Group`
+FROM `Eagle` AS `e`
+WHERE `e`.`Id` = @__p_0
+LIMIT 1
+""");
+ }
+
+ private void AssertSql(params string[] expected)
+ => Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
+}
diff --git a/test/EFCore.MySql.FunctionalTests/Query/TPCGearsOfWarQueryMySqlFixture.cs b/test/EFCore.MySql.FunctionalTests/Query/TPCGearsOfWarQueryMySqlFixture.cs
new file mode 100644
index 000000000..a73e28a02
--- /dev/null
+++ b/test/EFCore.MySql.FunctionalTests/Query/TPCGearsOfWarQueryMySqlFixture.cs
@@ -0,0 +1,43 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Query;
+using Microsoft.EntityFrameworkCore.TestModels.GearsOfWarModel;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+using Pomelo.EntityFrameworkCore.MySql.FunctionalTests.TestUtilities;
+
+namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests.Query;
+
+public class TPCGearsOfWarQueryMySqlFixture : TPCGearsOfWarQueryRelationalFixture, IQueryFixtureBase
+{
+ protected override ITestStoreFactory TestStoreFactory
+ => MySqlTestStoreFactory.Instance;
+
+ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder)
+ {
+ var optionsBuilder = base.AddOptions(builder);
+
+ new MySqlDbContextOptionsBuilder(optionsBuilder)
+ .EnableIndexOptimizedBooleanColumns(true);
+
+ return optionsBuilder;
+ }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
+ {
+ base.OnModelCreating(modelBuilder, context);
+
+ modelBuilder.Entity().HasIndex(e => e.IsAutomatic);
+ }
+
+ public new ISetSource GetExpectedData()
+ {
+ var data = (GearsOfWarData)base.GetExpectedData();
+
+ foreach (var mission in data.Missions)
+ {
+ mission.Timeline = MySqlTestHelpers.GetExpectedValue(mission.Timeline);
+ }
+
+ return data;
+ }
+}
diff --git a/test/EFCore.MySql.FunctionalTests/Query/TPCGearsOfWarQueryMySqlTest.cs b/test/EFCore.MySql.FunctionalTests/Query/TPCGearsOfWarQueryMySqlTest.cs
new file mode 100644
index 000000000..508b7a734
--- /dev/null
+++ b/test/EFCore.MySql.FunctionalTests/Query/TPCGearsOfWarQueryMySqlTest.cs
@@ -0,0 +1,13022 @@
+using System;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Query;
+using Microsoft.EntityFrameworkCore.TestModels.GearsOfWarModel;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+using Pomelo.EntityFrameworkCore.MySql.FunctionalTests.TestUtilities;
+using Pomelo.EntityFrameworkCore.MySql.Infrastructure;
+using Pomelo.EntityFrameworkCore.MySql.Tests.TestUtilities.Attributes;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Pomelo.EntityFrameworkCore.MySql.FunctionalTests.Query;
+
+public class TPCGearsOfWarQueryMySqlTest : TPCGearsOfWarQueryRelationalTestBase
+{
+ public TPCGearsOfWarQueryMySqlTest(TPCGearsOfWarQueryMySqlFixture fixture, ITestOutputHelper testOutputHelper)
+ : base(fixture)
+ {
+ Fixture.TestSqlLoggerFactory.Clear();
+ //Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
+ }
+
+ protected override bool CanExecuteQueryString
+ => true;
+
+ [ConditionalFact]
+ public virtual void Check_all_tests_overridden()
+ => TestHelpers.AssertAllMethodsOverridden(GetType());
+
+ public override async Task Negate_on_binary_expression(bool async)
+ {
+ await base.Negate_on_binary_expression(async);
+
+ AssertSql(
+"""
+SELECT `s`.`Id`, `s`.`Banner`, `s`.`Banner5`, `s`.`InternalNumber`, `s`.`Name`
+FROM `Squads` AS `s`
+WHERE `s`.`Id` = -(`s`.`Id` + `s`.`Id`)
+""");
+ }
+
+ public override async Task Negate_on_column(bool async)
+ {
+ await base.Negate_on_column(async);
+
+ AssertSql(
+"""
+SELECT `s`.`Id`, `s`.`Banner`, `s`.`Banner5`, `s`.`InternalNumber`, `s`.`Name`
+FROM `Squads` AS `s`
+WHERE `s`.`Id` = -`s`.`Id`
+""");
+ }
+
+ public override async Task Double_negate_on_column(bool async)
+ {
+ await base.Double_negate_on_column(async);
+
+ AssertSql(
+"""
+SELECT `s`.`Id`, `s`.`Banner`, `s`.`Banner5`, `s`.`InternalNumber`, `s`.`Name`
+FROM `Squads` AS `s`
+WHERE -(-`s`.`Id`) = `s`.`Id`
+""");
+ }
+
+ public override async Task Negate_on_like_expression(bool async)
+ {
+ await base.Negate_on_like_expression(async);
+
+ AssertSql(
+"""
+SELECT `s`.`Id`, `s`.`Banner`, `s`.`Banner5`, `s`.`InternalNumber`, `s`.`Name`
+FROM `Squads` AS `s`
+WHERE `s`.`Name` IS NOT NULL AND NOT (`s`.`Name` LIKE 'us%')
+""");
+ }
+
+ public override async Task Entity_equality_empty(bool async)
+ {
+ await base.Entity_equality_empty(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE FALSE
+""");
+ }
+
+ public override async Task Include_multiple_one_to_one_and_one_to_many(bool async)
+ {
+ await base.Include_multiple_one_to_one_and_one_to_many(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`GearNickName`, `t`.`GearSquadId`, `t`.`IssueDate`, `t`.`Note`, `t0`.`Nickname`, `t0`.`SquadId`, `t0`.`AssignedCityName`, `t0`.`CityOfBirthName`, `t0`.`FullName`, `t0`.`HasSoulPatch`, `t0`.`LeaderNickname`, `t0`.`LeaderSquadId`, `t0`.`Rank`, `t0`.`Discriminator`, `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+FROM `Tags` AS `t`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearNickName` = `t0`.`Nickname`) AND (`t`.`GearSquadId` = `t0`.`SquadId`)
+LEFT JOIN `Weapons` AS `w` ON `t0`.`FullName` = `w`.`OwnerFullName`
+ORDER BY `t`.`Id`, `t0`.`Nickname`, `t0`.`SquadId`
+""");
+ }
+
+ public override async Task Include_multiple_one_to_one_optional_and_one_to_one_required(bool async)
+ {
+ await base.Include_multiple_one_to_one_optional_and_one_to_one_required(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`GearNickName`, `t`.`GearSquadId`, `t`.`IssueDate`, `t`.`Note`, `t0`.`Nickname`, `t0`.`SquadId`, `t0`.`AssignedCityName`, `t0`.`CityOfBirthName`, `t0`.`FullName`, `t0`.`HasSoulPatch`, `t0`.`LeaderNickname`, `t0`.`LeaderSquadId`, `t0`.`Rank`, `t0`.`Discriminator`, `s`.`Id`, `s`.`Banner`, `s`.`Banner5`, `s`.`InternalNumber`, `s`.`Name`
+FROM `Tags` AS `t`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearNickName` = `t0`.`Nickname`) AND (`t`.`GearSquadId` = `t0`.`SquadId`)
+LEFT JOIN `Squads` AS `s` ON `t0`.`SquadId` = `s`.`Id`
+""");
+ }
+
+ public override async Task Include_multiple_circular(bool async)
+ {
+ await base.Include_multiple_circular(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`, `c`.`Name`, `c`.`Location`, `c`.`Nation`, `t0`.`Nickname`, `t0`.`SquadId`, `t0`.`AssignedCityName`, `t0`.`CityOfBirthName`, `t0`.`FullName`, `t0`.`HasSoulPatch`, `t0`.`LeaderNickname`, `t0`.`LeaderSquadId`, `t0`.`Rank`, `t0`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+INNER JOIN `Cities` AS `c` ON `t`.`CityOfBirthName` = `c`.`Name`
+LEFT JOIN (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`, `g0`.`AssignedCityName`, `g0`.`CityOfBirthName`, `g0`.`FullName`, `g0`.`HasSoulPatch`, `g0`.`LeaderNickname`, `g0`.`LeaderSquadId`, `g0`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o0`
+) AS `t0` ON `c`.`Name` = `t0`.`AssignedCityName`
+ORDER BY `t`.`Nickname`, `t`.`SquadId`, `c`.`Name`, `t0`.`Nickname`
+""");
+ }
+
+ public override async Task Include_multiple_circular_with_filter(bool async)
+ {
+ await base.Include_multiple_circular_with_filter(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`, `c`.`Name`, `c`.`Location`, `c`.`Nation`, `t0`.`Nickname`, `t0`.`SquadId`, `t0`.`AssignedCityName`, `t0`.`CityOfBirthName`, `t0`.`FullName`, `t0`.`HasSoulPatch`, `t0`.`LeaderNickname`, `t0`.`LeaderSquadId`, `t0`.`Rank`, `t0`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+INNER JOIN `Cities` AS `c` ON `t`.`CityOfBirthName` = `c`.`Name`
+LEFT JOIN (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`, `g0`.`AssignedCityName`, `g0`.`CityOfBirthName`, `g0`.`FullName`, `g0`.`HasSoulPatch`, `g0`.`LeaderNickname`, `g0`.`LeaderSquadId`, `g0`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o0`
+) AS `t0` ON `c`.`Name` = `t0`.`AssignedCityName`
+WHERE `t`.`Nickname` = 'Marcus'
+ORDER BY `t`.`Nickname`, `t`.`SquadId`, `c`.`Name`, `t0`.`Nickname`
+""");
+ }
+
+ public override async Task Include_using_alternate_key(bool async)
+ {
+ await base.Include_using_alternate_key(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`, `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+LEFT JOIN `Weapons` AS `w` ON `t`.`FullName` = `w`.`OwnerFullName`
+WHERE `t`.`Nickname` = 'Marcus'
+ORDER BY `t`.`Nickname`, `t`.`SquadId`
+""");
+ }
+
+ public override async Task Include_navigation_on_derived_type(bool async)
+ {
+ await base.Include_navigation_on_derived_type(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`, `t0`.`Nickname`, `t0`.`SquadId`, `t0`.`AssignedCityName`, `t0`.`CityOfBirthName`, `t0`.`FullName`, `t0`.`HasSoulPatch`, `t0`.`LeaderNickname`, `t0`.`LeaderSquadId`, `t0`.`Rank`, `t0`.`Discriminator`
+FROM (
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o0`
+) AS `t0` ON (`t`.`Nickname` = `t0`.`LeaderNickname`) AND (`t`.`SquadId` = `t0`.`LeaderSquadId`)
+ORDER BY `t`.`Nickname`, `t`.`SquadId`, `t0`.`Nickname`
+""");
+ }
+
+ public override async Task String_based_Include_navigation_on_derived_type(bool async)
+ {
+ await base.String_based_Include_navigation_on_derived_type(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`, `t0`.`Nickname`, `t0`.`SquadId`, `t0`.`AssignedCityName`, `t0`.`CityOfBirthName`, `t0`.`FullName`, `t0`.`HasSoulPatch`, `t0`.`LeaderNickname`, `t0`.`LeaderSquadId`, `t0`.`Rank`, `t0`.`Discriminator`
+FROM (
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o0`
+) AS `t0` ON (`t`.`Nickname` = `t0`.`LeaderNickname`) AND (`t`.`SquadId` = `t0`.`LeaderSquadId`)
+ORDER BY `t`.`Nickname`, `t`.`SquadId`, `t0`.`Nickname`
+""");
+ }
+
+ public override async Task Select_Where_Navigation_Included(bool async)
+ {
+ await base.Select_Where_Navigation_Included(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`GearNickName`, `t`.`GearSquadId`, `t`.`IssueDate`, `t`.`Note`, `t0`.`Nickname`, `t0`.`SquadId`, `t0`.`AssignedCityName`, `t0`.`CityOfBirthName`, `t0`.`FullName`, `t0`.`HasSoulPatch`, `t0`.`LeaderNickname`, `t0`.`LeaderSquadId`, `t0`.`Rank`, `t0`.`Discriminator`
+FROM `Tags` AS `t`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearNickName` = `t0`.`Nickname`) AND (`t`.`GearSquadId` = `t0`.`SquadId`)
+WHERE `t0`.`Nickname` = 'Marcus'
+""");
+ }
+
+ public override async Task Include_with_join_reference1(bool async)
+ {
+ await base.Include_with_join_reference1(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`, `c`.`Name`, `c`.`Location`, `c`.`Nation`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+INNER JOIN `Tags` AS `t0` ON (`t`.`SquadId` = `t0`.`GearSquadId`) AND (`t`.`Nickname` = `t0`.`GearNickName`)
+INNER JOIN `Cities` AS `c` ON `t`.`CityOfBirthName` = `c`.`Name`
+""");
+ }
+
+ public override async Task Include_with_join_reference2(bool async)
+ {
+ await base.Include_with_join_reference2(async);
+
+ AssertSql(
+"""
+SELECT `t0`.`Nickname`, `t0`.`SquadId`, `t0`.`AssignedCityName`, `t0`.`CityOfBirthName`, `t0`.`FullName`, `t0`.`HasSoulPatch`, `t0`.`LeaderNickname`, `t0`.`LeaderSquadId`, `t0`.`Rank`, `t0`.`Discriminator`, `c`.`Name`, `c`.`Location`, `c`.`Nation`
+FROM `Tags` AS `t`
+INNER JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearSquadId` = `t0`.`SquadId`) AND (`t`.`GearNickName` = `t0`.`Nickname`)
+INNER JOIN `Cities` AS `c` ON `t0`.`CityOfBirthName` = `c`.`Name`
+""");
+ }
+
+ public override async Task Include_with_join_collection1(bool async)
+ {
+ await base.Include_with_join_collection1(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`, `t0`.`Id`, `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+INNER JOIN `Tags` AS `t0` ON (`t`.`SquadId` = `t0`.`GearSquadId`) AND (`t`.`Nickname` = `t0`.`GearNickName`)
+LEFT JOIN `Weapons` AS `w` ON `t`.`FullName` = `w`.`OwnerFullName`
+ORDER BY `t`.`Nickname`, `t`.`SquadId`, `t0`.`Id`
+""");
+ }
+
+ public override async Task Include_with_join_collection2(bool async)
+ {
+ await base.Include_with_join_collection2(async);
+
+ AssertSql(
+"""
+SELECT `t0`.`Nickname`, `t0`.`SquadId`, `t0`.`AssignedCityName`, `t0`.`CityOfBirthName`, `t0`.`FullName`, `t0`.`HasSoulPatch`, `t0`.`LeaderNickname`, `t0`.`LeaderSquadId`, `t0`.`Rank`, `t0`.`Discriminator`, `t`.`Id`, `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+FROM `Tags` AS `t`
+INNER JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearSquadId` = `t0`.`SquadId`) AND (`t`.`GearNickName` = `t0`.`Nickname`)
+LEFT JOIN `Weapons` AS `w` ON `t0`.`FullName` = `w`.`OwnerFullName`
+ORDER BY `t`.`Id`, `t0`.`Nickname`, `t0`.`SquadId`
+""");
+ }
+
+ public override async Task Include_where_list_contains_navigation(bool async)
+ {
+ await base.Include_where_list_contains_navigation(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`
+FROM `Tags` AS `t`
+""",
+ //
+ """
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`, `t0`.`Id`, `t0`.`GearNickName`, `t0`.`GearSquadId`, `t0`.`IssueDate`, `t0`.`Note`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+LEFT JOIN `Tags` AS `t0` ON (`t`.`Nickname` = `t0`.`GearNickName`) AND (`t`.`SquadId` = `t0`.`GearSquadId`)
+WHERE `t0`.`Id` IS NOT NULL AND `t0`.`Id` IN ('b39a6fba-9026-4d69-828e-fd7068673e57', '70534e05-782c-4052-8720-c2c54481ce5f', 'a8ad98f9-e023-4e2a-9a70-c2728455bd34', 'df36f493-463f-4123-83f9-6b135deeb7ba', '34c8d86e-a4ac-4be5-827f-584dda348a07', 'a7be028a-0cf2-448f-ab55-ce8bc5d8cf69')
+""");
+ }
+
+ public override async Task Include_where_list_contains_navigation2(bool async)
+ {
+ await base.Include_where_list_contains_navigation2(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`
+FROM `Tags` AS `t`
+""",
+ //
+ """
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`, `t0`.`Id`, `t0`.`GearNickName`, `t0`.`GearSquadId`, `t0`.`IssueDate`, `t0`.`Note`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+INNER JOIN `Cities` AS `c` ON `t`.`CityOfBirthName` = `c`.`Name`
+LEFT JOIN `Tags` AS `t0` ON (`t`.`Nickname` = `t0`.`GearNickName`) AND (`t`.`SquadId` = `t0`.`GearSquadId`)
+WHERE `c`.`Location` IS NOT NULL AND `t0`.`Id` IN ('b39a6fba-9026-4d69-828e-fd7068673e57', '70534e05-782c-4052-8720-c2c54481ce5f', 'a8ad98f9-e023-4e2a-9a70-c2728455bd34', 'df36f493-463f-4123-83f9-6b135deeb7ba', '34c8d86e-a4ac-4be5-827f-584dda348a07', 'a7be028a-0cf2-448f-ab55-ce8bc5d8cf69')
+""");
+ }
+
+ public override async Task Navigation_accessed_twice_outside_and_inside_subquery(bool async)
+ {
+ await base.Navigation_accessed_twice_outside_and_inside_subquery(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`
+FROM `Tags` AS `t`
+""",
+ //
+ """
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+LEFT JOIN `Tags` AS `t0` ON (`t`.`Nickname` = `t0`.`GearNickName`) AND (`t`.`SquadId` = `t0`.`GearSquadId`)
+WHERE `t0`.`Id` IS NOT NULL AND `t0`.`Id` IN ('b39a6fba-9026-4d69-828e-fd7068673e57', '70534e05-782c-4052-8720-c2c54481ce5f', 'a8ad98f9-e023-4e2a-9a70-c2728455bd34', 'df36f493-463f-4123-83f9-6b135deeb7ba', '34c8d86e-a4ac-4be5-827f-584dda348a07', 'a7be028a-0cf2-448f-ab55-ce8bc5d8cf69')
+""");
+ }
+
+ public override async Task Include_with_join_multi_level(bool async)
+ {
+ await base.Include_with_join_multi_level(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`, `c`.`Name`, `c`.`Location`, `c`.`Nation`, `t0`.`Id`, `t1`.`Nickname`, `t1`.`SquadId`, `t1`.`AssignedCityName`, `t1`.`CityOfBirthName`, `t1`.`FullName`, `t1`.`HasSoulPatch`, `t1`.`LeaderNickname`, `t1`.`LeaderSquadId`, `t1`.`Rank`, `t1`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+INNER JOIN `Tags` AS `t0` ON (`t`.`SquadId` = `t0`.`GearSquadId`) AND (`t`.`Nickname` = `t0`.`GearNickName`)
+INNER JOIN `Cities` AS `c` ON `t`.`CityOfBirthName` = `c`.`Name`
+LEFT JOIN (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`, `g0`.`AssignedCityName`, `g0`.`CityOfBirthName`, `g0`.`FullName`, `g0`.`HasSoulPatch`, `g0`.`LeaderNickname`, `g0`.`LeaderSquadId`, `g0`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o0`
+) AS `t1` ON `c`.`Name` = `t1`.`AssignedCityName`
+ORDER BY `t`.`Nickname`, `t`.`SquadId`, `t0`.`Id`, `c`.`Name`, `t1`.`Nickname`
+""");
+ }
+
+ public override async Task Include_with_join_and_inheritance1(bool async)
+ {
+ await base.Include_with_join_and_inheritance1(async);
+
+ AssertSql(
+"""
+SELECT `t0`.`Nickname`, `t0`.`SquadId`, `t0`.`AssignedCityName`, `t0`.`CityOfBirthName`, `t0`.`FullName`, `t0`.`HasSoulPatch`, `t0`.`LeaderNickname`, `t0`.`LeaderSquadId`, `t0`.`Rank`, `t0`.`Discriminator`, `c`.`Name`, `c`.`Location`, `c`.`Nation`
+FROM `Tags` AS `t`
+INNER JOIN (
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearSquadId` = `t0`.`SquadId`) AND (`t`.`GearNickName` = `t0`.`Nickname`)
+INNER JOIN `Cities` AS `c` ON `t0`.`CityOfBirthName` = `c`.`Name`
+""");
+ }
+
+ public override async Task Include_with_join_and_inheritance_with_orderby_before_and_after_include(bool async)
+ {
+ await base.Include_with_join_and_inheritance_with_orderby_before_and_after_include(async);
+
+ AssertSql(
+"""
+SELECT `t0`.`Nickname`, `t0`.`SquadId`, `t0`.`AssignedCityName`, `t0`.`CityOfBirthName`, `t0`.`FullName`, `t0`.`HasSoulPatch`, `t0`.`LeaderNickname`, `t0`.`LeaderSquadId`, `t0`.`Rank`, `t0`.`Discriminator`, `t`.`Id`, `t1`.`Nickname`, `t1`.`SquadId`, `t1`.`AssignedCityName`, `t1`.`CityOfBirthName`, `t1`.`FullName`, `t1`.`HasSoulPatch`, `t1`.`LeaderNickname`, `t1`.`LeaderSquadId`, `t1`.`Rank`, `t1`.`Discriminator`
+FROM `Tags` AS `t`
+INNER JOIN (
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearSquadId` = `t0`.`SquadId`) AND (`t`.`GearNickName` = `t0`.`Nickname`)
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o0`
+) AS `t1` ON (`t0`.`Nickname` = `t1`.`LeaderNickname`) AND (`t0`.`SquadId` = `t1`.`LeaderSquadId`)
+ORDER BY `t0`.`HasSoulPatch`, `t0`.`Nickname` DESC, `t`.`Id`, `t0`.`SquadId`, `t1`.`Nickname`
+""");
+ }
+
+ public override async Task Include_with_join_and_inheritance2(bool async)
+ {
+ await base.Include_with_join_and_inheritance2(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`, `t0`.`Id`, `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+FROM (
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+INNER JOIN `Tags` AS `t0` ON (`t`.`SquadId` = `t0`.`GearSquadId`) AND (`t`.`Nickname` = `t0`.`GearNickName`)
+LEFT JOIN `Weapons` AS `w` ON `t`.`FullName` = `w`.`OwnerFullName`
+ORDER BY `t`.`Nickname`, `t`.`SquadId`, `t0`.`Id`
+""");
+ }
+
+ public override async Task Include_with_join_and_inheritance3(bool async)
+ {
+ await base.Include_with_join_and_inheritance3(async);
+
+ AssertSql(
+"""
+SELECT `t0`.`Nickname`, `t0`.`SquadId`, `t0`.`AssignedCityName`, `t0`.`CityOfBirthName`, `t0`.`FullName`, `t0`.`HasSoulPatch`, `t0`.`LeaderNickname`, `t0`.`LeaderSquadId`, `t0`.`Rank`, `t0`.`Discriminator`, `t`.`Id`, `t1`.`Nickname`, `t1`.`SquadId`, `t1`.`AssignedCityName`, `t1`.`CityOfBirthName`, `t1`.`FullName`, `t1`.`HasSoulPatch`, `t1`.`LeaderNickname`, `t1`.`LeaderSquadId`, `t1`.`Rank`, `t1`.`Discriminator`
+FROM `Tags` AS `t`
+INNER JOIN (
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearSquadId` = `t0`.`SquadId`) AND (`t`.`GearNickName` = `t0`.`Nickname`)
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o0`
+) AS `t1` ON (`t0`.`Nickname` = `t1`.`LeaderNickname`) AND (`t0`.`SquadId` = `t1`.`LeaderSquadId`)
+ORDER BY `t`.`Id`, `t0`.`Nickname`, `t0`.`SquadId`, `t1`.`Nickname`
+""");
+ }
+
+ public override async Task Include_with_nested_navigation_in_order_by(bool async)
+ {
+ await base.Include_with_nested_navigation_in_order_by(async);
+
+ AssertSql(
+"""
+SELECT `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`, `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM `Weapons` AS `w`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t` ON `w`.`OwnerFullName` = `t`.`FullName`
+LEFT JOIN `Cities` AS `c` ON `t`.`CityOfBirthName` = `c`.`Name`
+WHERE (`t`.`Nickname` <> 'Paduk') OR `t`.`Nickname` IS NULL
+ORDER BY `c`.`Name`, `w`.`Id`
+""");
+ }
+
+ public override async Task Where_enum(bool async)
+ {
+ await base.Where_enum(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE `t`.`Rank` = 4
+""");
+ }
+
+ public override async Task Where_nullable_enum_with_constant(bool async)
+ {
+ await base.Where_nullable_enum_with_constant(async);
+
+ AssertSql(
+"""
+SELECT `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+FROM `Weapons` AS `w`
+WHERE `w`.`AmmunitionType` = 1
+""");
+ }
+
+ public override async Task Where_nullable_enum_with_null_constant(bool async)
+ {
+ await base.Where_nullable_enum_with_null_constant(async);
+
+ AssertSql(
+"""
+SELECT `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+FROM `Weapons` AS `w`
+WHERE `w`.`AmmunitionType` IS NULL
+""");
+ }
+
+ public override async Task Where_nullable_enum_with_non_nullable_parameter(bool async)
+ {
+ await base.Where_nullable_enum_with_non_nullable_parameter(async);
+
+ AssertSql(
+"""
+@__ammunitionType_0='1'
+
+SELECT `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+FROM `Weapons` AS `w`
+WHERE `w`.`AmmunitionType` = @__ammunitionType_0
+""");
+ }
+
+ public override async Task Where_nullable_enum_with_nullable_parameter(bool async)
+ {
+ await base.Where_nullable_enum_with_nullable_parameter(async);
+
+ AssertSql(
+"""
+@__ammunitionType_0='1' (Nullable = true)
+
+SELECT `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+FROM `Weapons` AS `w`
+WHERE `w`.`AmmunitionType` = @__ammunitionType_0
+""",
+ //
+ """
+SELECT `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+FROM `Weapons` AS `w`
+WHERE `w`.`AmmunitionType` IS NULL
+""");
+ }
+
+ public override async Task Where_bitwise_and_enum(bool async)
+ {
+ await base.Where_bitwise_and_enum(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (`t`.`Rank` & 2) > 0
+""",
+ //
+ """
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (`t`.`Rank` & 2) = 2
+""");
+ }
+
+ public override async Task Where_bitwise_and_integral(bool async)
+ {
+ await base.Where_bitwise_and_integral(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (`t`.`Rank` & 1) = 1
+""",
+ //
+ """
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (CAST(`t`.`Rank` AS signed) & 1) = 1
+""",
+ //
+ """
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (CAST(`t`.`Rank` AS signed) & 1) = 1
+""");
+ }
+
+ public override async Task Where_bitwise_and_nullable_enum_with_constant(bool async)
+ {
+ await base.Where_bitwise_and_nullable_enum_with_constant(async);
+
+ AssertSql(
+"""
+SELECT `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+FROM `Weapons` AS `w`
+WHERE (`w`.`AmmunitionType` & 1) > 0
+""");
+ }
+
+ public override async Task Where_bitwise_and_nullable_enum_with_null_constant(bool async)
+ {
+ await base.Where_bitwise_and_nullable_enum_with_null_constant(async);
+
+ AssertSql(
+"""
+SELECT `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+FROM `Weapons` AS `w`
+WHERE (`w`.`AmmunitionType` & NULL) > 0
+""");
+ }
+
+ public override async Task Where_bitwise_and_nullable_enum_with_non_nullable_parameter(bool async)
+ {
+ await base.Where_bitwise_and_nullable_enum_with_non_nullable_parameter(async);
+
+ AssertSql(
+"""
+@__ammunitionType_0='1'
+
+SELECT `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+FROM `Weapons` AS `w`
+WHERE (`w`.`AmmunitionType` & @__ammunitionType_0) > 0
+""");
+ }
+
+ public override async Task Where_bitwise_and_nullable_enum_with_nullable_parameter(bool async)
+ {
+ await base.Where_bitwise_and_nullable_enum_with_nullable_parameter(async);
+
+ AssertSql(
+"""
+@__ammunitionType_0='1' (Nullable = true)
+
+SELECT `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+FROM `Weapons` AS `w`
+WHERE (`w`.`AmmunitionType` & @__ammunitionType_0) > 0
+""",
+ //
+ """
+SELECT `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+FROM `Weapons` AS `w`
+WHERE (`w`.`AmmunitionType` & NULL) > 0
+""");
+ }
+
+ public override async Task Where_bitwise_or_enum(bool async)
+ {
+ await base.Where_bitwise_or_enum(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (`t`.`Rank` | 2) > 0
+""");
+ }
+
+ public override async Task Bitwise_projects_values_in_select(bool async)
+ {
+ await base.Bitwise_projects_values_in_select(async);
+
+ AssertSql(
+"""
+SELECT (`t`.`Rank` & 2) = 2 AS `BitwiseTrue`, (`t`.`Rank` & 2) = 4 AS `BitwiseFalse`, `t`.`Rank` & 2 AS `BitwiseValue`
+FROM (
+ SELECT `g`.`Rank`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Rank`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (`t`.`Rank` & 2) = 2
+LIMIT 1
+""");
+ }
+
+ public override async Task Where_enum_has_flag(bool async)
+ {
+ await base.Where_enum_has_flag(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (`t`.`Rank` & 2) = 2
+""",
+ //
+ """
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (`t`.`Rank` & 18) = 18
+""",
+ //
+ """
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (`t`.`Rank` & 1) = 1
+""",
+ //
+ """
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (`t`.`Rank` & 1) = 1
+""",
+ //
+ """
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (2 & `t`.`Rank`) = `t`.`Rank`
+""");
+ }
+
+ public override async Task Where_enum_has_flag_subquery(bool async)
+ {
+ await base.Where_enum_has_flag_subquery(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (`t`.`Rank` & COALESCE((
+ SELECT `t0`.`Rank`
+ FROM (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`, `g0`.`AssignedCityName`, `g0`.`CityOfBirthName`, `g0`.`FullName`, `g0`.`HasSoulPatch`, `g0`.`LeaderNickname`, `g0`.`LeaderSquadId`, `g0`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o0`
+ ) AS `t0`
+ ORDER BY `t0`.`Nickname`, `t0`.`SquadId`
+ LIMIT 1), 0)) = COALESCE((
+ SELECT `t0`.`Rank`
+ FROM (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`, `g0`.`AssignedCityName`, `g0`.`CityOfBirthName`, `g0`.`FullName`, `g0`.`HasSoulPatch`, `g0`.`LeaderNickname`, `g0`.`LeaderSquadId`, `g0`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o0`
+ ) AS `t0`
+ ORDER BY `t0`.`Nickname`, `t0`.`SquadId`
+ LIMIT 1), 0)
+""",
+ //
+ """
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (2 & COALESCE((
+ SELECT `t0`.`Rank`
+ FROM (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`, `g0`.`AssignedCityName`, `g0`.`CityOfBirthName`, `g0`.`FullName`, `g0`.`HasSoulPatch`, `g0`.`LeaderNickname`, `g0`.`LeaderSquadId`, `g0`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o0`
+ ) AS `t0`
+ ORDER BY `t0`.`Nickname`, `t0`.`SquadId`
+ LIMIT 1), 0)) = COALESCE((
+ SELECT `t0`.`Rank`
+ FROM (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`, `g0`.`AssignedCityName`, `g0`.`CityOfBirthName`, `g0`.`FullName`, `g0`.`HasSoulPatch`, `g0`.`LeaderNickname`, `g0`.`LeaderSquadId`, `g0`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o0`
+ ) AS `t0`
+ ORDER BY `t0`.`Nickname`, `t0`.`SquadId`
+ LIMIT 1), 0)
+""");
+ }
+
+ public override async Task Where_enum_has_flag_subquery_with_pushdown(bool async)
+ {
+ await base.Where_enum_has_flag_subquery_with_pushdown(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE ((`t`.`Rank` & (
+ SELECT `t0`.`Rank`
+ FROM (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`, `g0`.`AssignedCityName`, `g0`.`CityOfBirthName`, `g0`.`FullName`, `g0`.`HasSoulPatch`, `g0`.`LeaderNickname`, `g0`.`LeaderSquadId`, `g0`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o0`
+ ) AS `t0`
+ ORDER BY `t0`.`Nickname`, `t0`.`SquadId`
+ LIMIT 1)) = (
+ SELECT `t0`.`Rank`
+ FROM (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`, `g0`.`AssignedCityName`, `g0`.`CityOfBirthName`, `g0`.`FullName`, `g0`.`HasSoulPatch`, `g0`.`LeaderNickname`, `g0`.`LeaderSquadId`, `g0`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o0`
+ ) AS `t0`
+ ORDER BY `t0`.`Nickname`, `t0`.`SquadId`
+ LIMIT 1)) OR (
+ SELECT `t0`.`Rank`
+ FROM (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`, `g0`.`AssignedCityName`, `g0`.`CityOfBirthName`, `g0`.`FullName`, `g0`.`HasSoulPatch`, `g0`.`LeaderNickname`, `g0`.`LeaderSquadId`, `g0`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o0`
+ ) AS `t0`
+ ORDER BY `t0`.`Nickname`, `t0`.`SquadId`
+ LIMIT 1) IS NULL
+""",
+ //
+ """
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE ((2 & (
+ SELECT `t0`.`Rank`
+ FROM (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`, `g0`.`AssignedCityName`, `g0`.`CityOfBirthName`, `g0`.`FullName`, `g0`.`HasSoulPatch`, `g0`.`LeaderNickname`, `g0`.`LeaderSquadId`, `g0`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o0`
+ ) AS `t0`
+ ORDER BY `t0`.`Nickname`, `t0`.`SquadId`
+ LIMIT 1)) = (
+ SELECT `t0`.`Rank`
+ FROM (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`, `g0`.`AssignedCityName`, `g0`.`CityOfBirthName`, `g0`.`FullName`, `g0`.`HasSoulPatch`, `g0`.`LeaderNickname`, `g0`.`LeaderSquadId`, `g0`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o0`
+ ) AS `t0`
+ ORDER BY `t0`.`Nickname`, `t0`.`SquadId`
+ LIMIT 1)) OR (
+ SELECT `t0`.`Rank`
+ FROM (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`, `g0`.`AssignedCityName`, `g0`.`CityOfBirthName`, `g0`.`FullName`, `g0`.`HasSoulPatch`, `g0`.`LeaderNickname`, `g0`.`LeaderSquadId`, `g0`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o0`
+ ) AS `t0`
+ ORDER BY `t0`.`Nickname`, `t0`.`SquadId`
+ LIMIT 1) IS NULL
+""");
+ }
+
+ public override async Task Where_enum_has_flag_subquery_client_eval(bool async)
+ {
+ await base.Where_enum_has_flag_subquery_client_eval(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE ((`t`.`Rank` & (
+ SELECT `t0`.`Rank`
+ FROM (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`, `g0`.`AssignedCityName`, `g0`.`CityOfBirthName`, `g0`.`FullName`, `g0`.`HasSoulPatch`, `g0`.`LeaderNickname`, `g0`.`LeaderSquadId`, `g0`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o0`
+ ) AS `t0`
+ ORDER BY `t0`.`Nickname`, `t0`.`SquadId`
+ LIMIT 1)) = (
+ SELECT `t0`.`Rank`
+ FROM (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`, `g0`.`AssignedCityName`, `g0`.`CityOfBirthName`, `g0`.`FullName`, `g0`.`HasSoulPatch`, `g0`.`LeaderNickname`, `g0`.`LeaderSquadId`, `g0`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o0`
+ ) AS `t0`
+ ORDER BY `t0`.`Nickname`, `t0`.`SquadId`
+ LIMIT 1)) OR (
+ SELECT `t0`.`Rank`
+ FROM (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`, `g0`.`AssignedCityName`, `g0`.`CityOfBirthName`, `g0`.`FullName`, `g0`.`HasSoulPatch`, `g0`.`LeaderNickname`, `g0`.`LeaderSquadId`, `g0`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o0`
+ ) AS `t0`
+ ORDER BY `t0`.`Nickname`, `t0`.`SquadId`
+ LIMIT 1) IS NULL
+""");
+ }
+
+ public override async Task Where_enum_has_flag_with_non_nullable_parameter(bool async)
+ {
+ await base.Where_enum_has_flag_with_non_nullable_parameter(async);
+
+ AssertSql(
+"""
+@__parameter_0='2'
+
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (`t`.`Rank` & @__parameter_0) = @__parameter_0
+""");
+ }
+
+ public override async Task Where_has_flag_with_nullable_parameter(bool async)
+ {
+ await base.Where_has_flag_with_nullable_parameter(async);
+
+ AssertSql(
+"""
+@__parameter_0='2' (Nullable = true)
+
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (`t`.`Rank` & @__parameter_0) = @__parameter_0
+""");
+ }
+
+ public override async Task Select_enum_has_flag(bool async)
+ {
+ await base.Select_enum_has_flag(async);
+
+ AssertSql(
+"""
+SELECT (`t`.`Rank` & 2) = 2 AS `hasFlagTrue`, (`t`.`Rank` & 4) = 4 AS `hasFlagFalse`
+FROM (
+ SELECT `g`.`Rank`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Rank`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (`t`.`Rank` & 2) = 2
+LIMIT 1
+""");
+ }
+
+ public override async Task Where_count_subquery_without_collision(bool async)
+ {
+ await base.Where_count_subquery_without_collision(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (
+ SELECT COUNT(*)
+ FROM `Weapons` AS `w`
+ WHERE `t`.`FullName` = `w`.`OwnerFullName`) = 2
+""");
+ }
+
+ public override async Task Where_any_subquery_without_collision(bool async)
+ {
+ await base.Where_any_subquery_without_collision(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE EXISTS (
+ SELECT 1
+ FROM `Weapons` AS `w`
+ WHERE `t`.`FullName` = `w`.`OwnerFullName`)
+""");
+ }
+
+ public override async Task Select_inverted_boolean(bool async)
+ {
+ await base.Select_inverted_boolean(async);
+
+ AssertSql(
+"""
+SELECT `w`.`Id`, `w`.`IsAutomatic` = FALSE AS `Manual`
+FROM `Weapons` AS `w`
+WHERE `w`.`IsAutomatic` = TRUE
+""");
+ }
+
+ public override async Task Select_comparison_with_null(bool async)
+ {
+ await base.Select_comparison_with_null(async);
+
+ AssertSql(
+"""
+@__ammunitionType_0='1' (Nullable = true)
+
+SELECT `w`.`Id`, (`w`.`AmmunitionType` = @__ammunitionType_0) AND `w`.`AmmunitionType` IS NOT NULL AS `Cartridge`
+FROM `Weapons` AS `w`
+WHERE `w`.`AmmunitionType` = @__ammunitionType_0
+""",
+ //
+ """
+SELECT `w`.`Id`, `w`.`AmmunitionType` IS NULL AS `Cartridge`
+FROM `Weapons` AS `w`
+WHERE `w`.`AmmunitionType` IS NULL
+""");
+ }
+
+ public override async Task Select_null_parameter(bool async)
+ {
+ await base.Select_null_parameter(async);
+
+ AssertSql(
+"""
+@__ammunitionType_0='1' (Nullable = true)
+
+SELECT `w`.`Id`, @__ammunitionType_0 AS `AmmoType`
+FROM `Weapons` AS `w`
+""",
+ //
+ """
+SELECT `w`.`Id`, NULL AS `AmmoType`
+FROM `Weapons` AS `w`
+""",
+ //
+ """
+@__ammunitionType_0='2' (Nullable = true)
+
+SELECT `w`.`Id`, @__ammunitionType_0 AS `AmmoType`
+FROM `Weapons` AS `w`
+""",
+ //
+ """
+SELECT `w`.`Id`, NULL AS `AmmoType`
+FROM `Weapons` AS `w`
+""");
+ }
+
+ public override async Task Select_ternary_operation_with_boolean(bool async)
+ {
+ await base.Select_ternary_operation_with_boolean(async);
+
+ AssertSql(
+"""
+SELECT `w`.`Id`, CASE
+ WHEN `w`.`IsAutomatic` = TRUE THEN 1
+ ELSE 0
+END AS `Num`
+FROM `Weapons` AS `w`
+""");
+ }
+
+ public override async Task Select_ternary_operation_with_inverted_boolean(bool async)
+ {
+ await base.Select_ternary_operation_with_inverted_boolean(async);
+
+ AssertSql(
+"""
+SELECT `w`.`Id`, CASE
+ WHEN `w`.`IsAutomatic` = FALSE THEN 1
+ ELSE 0
+END AS `Num`
+FROM `Weapons` AS `w`
+""");
+ }
+
+ public override async Task Select_ternary_operation_with_has_value_not_null(bool async)
+ {
+ await base.Select_ternary_operation_with_has_value_not_null(async);
+
+ AssertSql(
+"""
+SELECT `w`.`Id`, CASE
+ WHEN `w`.`AmmunitionType` IS NOT NULL AND (`w`.`AmmunitionType` = 1) THEN 'Yes'
+ ELSE 'No'
+END AS `IsCartridge`
+FROM `Weapons` AS `w`
+WHERE `w`.`AmmunitionType` IS NOT NULL AND (`w`.`AmmunitionType` = 1)
+""");
+ }
+
+ public override async Task Select_ternary_operation_multiple_conditions(bool async)
+ {
+ await base.Select_ternary_operation_multiple_conditions(async);
+
+ AssertSql(
+"""
+SELECT `w`.`Id`, CASE
+ WHEN (`w`.`AmmunitionType` = 2) AND (`w`.`SynergyWithId` = 1) THEN 'Yes'
+ ELSE 'No'
+END AS `IsCartridge`
+FROM `Weapons` AS `w`
+""");
+ }
+
+ public override async Task Select_ternary_operation_multiple_conditions_2(bool async)
+ {
+ await base.Select_ternary_operation_multiple_conditions_2(async);
+
+ AssertSql(
+"""
+SELECT `w`.`Id`, CASE
+ WHEN (`w`.`IsAutomatic` = FALSE) AND (`w`.`SynergyWithId` = 1) THEN 'Yes'
+ ELSE 'No'
+END AS `IsCartridge`
+FROM `Weapons` AS `w`
+""");
+ }
+
+ public override async Task Select_multiple_conditions(bool async)
+ {
+ await base.Select_multiple_conditions(async);
+
+ AssertSql(
+"""
+SELECT `w`.`Id`, (`w`.`IsAutomatic` = FALSE) AND ((`w`.`SynergyWithId` = 1) AND `w`.`SynergyWithId` IS NOT NULL) AS `IsCartridge`
+FROM `Weapons` AS `w`
+""");
+ }
+
+ public override async Task Select_nested_ternary_operations(bool async)
+ {
+ await base.Select_nested_ternary_operations(async);
+
+ AssertSql(
+"""
+SELECT `w`.`Id`, CASE
+ WHEN `w`.`IsAutomatic` = FALSE THEN CASE
+ WHEN `w`.`AmmunitionType` = 1 THEN 'ManualCartridge'
+ ELSE 'Manual'
+ END
+ ELSE 'Auto'
+END AS `IsManualCartridge`
+FROM `Weapons` AS `w`
+""");
+ }
+
+ public override async Task Null_propagation_optimization1(bool async)
+ {
+ await base.Null_propagation_optimization1(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (`t`.`LeaderNickname` = 'Marcus') AND `t`.`LeaderNickname` IS NOT NULL
+""");
+ }
+
+ public override async Task Null_propagation_optimization2(bool async)
+ {
+ await base.Null_propagation_optimization2(async);
+
+ // issue #16050
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE CASE
+ WHEN `t`.`LeaderNickname` IS NULL THEN NULL
+ ELSE `t`.`LeaderNickname` IS NOT NULL AND (`t`.`LeaderNickname` LIKE '%us')
+END = TRUE
+""");
+ }
+
+ public override async Task Null_propagation_optimization3(bool async)
+ {
+ await base.Null_propagation_optimization3(async);
+
+ // issue #16050
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE CASE
+ WHEN `t`.`LeaderNickname` IS NOT NULL THEN `t`.`LeaderNickname` LIKE '%us'
+ ELSE NULL
+END = TRUE
+""");
+ }
+
+ public override async Task Null_propagation_optimization4(bool async)
+ {
+ await base.Null_propagation_optimization4(async);
+
+ // issue #16050
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (CASE
+ WHEN `t`.`LeaderNickname` IS NULL THEN NULL
+ ELSE CHAR_LENGTH(`t`.`LeaderNickname`)
+END = 5) AND CASE
+ WHEN `t`.`LeaderNickname` IS NULL THEN NULL
+ ELSE CHAR_LENGTH(`t`.`LeaderNickname`)
+END IS NOT NULL
+""");
+ }
+
+ public override async Task Null_propagation_optimization5(bool async)
+ {
+ await base.Null_propagation_optimization5(async);
+
+ // issue #16050
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (CASE
+ WHEN `t`.`LeaderNickname` IS NOT NULL THEN CHAR_LENGTH(`t`.`LeaderNickname`)
+ ELSE NULL
+END = 5) AND CASE
+ WHEN `t`.`LeaderNickname` IS NOT NULL THEN CHAR_LENGTH(`t`.`LeaderNickname`)
+ ELSE NULL
+END IS NOT NULL
+""");
+ }
+
+ public override async Task Null_propagation_optimization6(bool async)
+ {
+ await base.Null_propagation_optimization6(async);
+
+ // issue #16050
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (CASE
+ WHEN `t`.`LeaderNickname` IS NOT NULL THEN CHAR_LENGTH(`t`.`LeaderNickname`)
+ ELSE NULL
+END = 5) AND CASE
+ WHEN `t`.`LeaderNickname` IS NOT NULL THEN CHAR_LENGTH(`t`.`LeaderNickname`)
+ ELSE NULL
+END IS NOT NULL
+""");
+ }
+
+ public override async Task Select_null_propagation_optimization7(bool async)
+ {
+ await base.Select_null_propagation_optimization7(async);
+
+ // issue #16050
+ AssertSql(
+"""
+SELECT CASE
+ WHEN `t`.`LeaderNickname` IS NOT NULL THEN CONCAT(`t`.`LeaderNickname`, `t`.`LeaderNickname`)
+ ELSE NULL
+END
+FROM (
+ SELECT `g`.`LeaderNickname`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`LeaderNickname`
+ FROM `Officers` AS `o`
+) AS `t`
+""");
+ }
+
+ public override async Task Select_null_propagation_optimization8(bool async)
+ {
+ await base.Select_null_propagation_optimization8(async);
+
+ AssertSql(
+"""
+SELECT CONCAT(COALESCE(`t`.`LeaderNickname`, ''), COALESCE(`t`.`LeaderNickname`, ''))
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`LeaderNickname`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`LeaderNickname`
+ FROM `Officers` AS `o`
+) AS `t`
+""");
+ }
+
+ public override async Task Select_null_propagation_optimization9(bool async)
+ {
+ await base.Select_null_propagation_optimization9(async);
+
+ AssertSql(
+"""
+SELECT CHAR_LENGTH(`t`.`FullName`)
+FROM (
+ SELECT `g`.`FullName`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`FullName`
+ FROM `Officers` AS `o`
+) AS `t`
+""");
+ }
+
+ public override async Task Select_null_propagation_negative1(bool async)
+ {
+ await base.Select_null_propagation_negative1(async);
+
+ AssertSql(
+"""
+SELECT CASE
+ WHEN `t`.`LeaderNickname` IS NOT NULL THEN CHAR_LENGTH(`t`.`Nickname`) = 5
+ ELSE NULL
+END
+FROM (
+ SELECT `g`.`Nickname`, `g`.`LeaderNickname`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`LeaderNickname`
+ FROM `Officers` AS `o`
+) AS `t`
+""");
+ }
+
+ public override async Task Select_null_propagation_negative2(bool async)
+ {
+ await base.Select_null_propagation_negative2(async);
+
+ AssertSql(
+"""
+SELECT CASE
+ WHEN `t`.`LeaderNickname` IS NOT NULL THEN `t0`.`LeaderNickname`
+ ELSE NULL
+END
+FROM (
+ SELECT `g`.`LeaderNickname`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`LeaderNickname`
+ FROM `Officers` AS `o`
+) AS `t`
+CROSS JOIN (
+ SELECT `g0`.`LeaderNickname`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`LeaderNickname`
+ FROM `Officers` AS `o0`
+) AS `t0`
+""");
+ }
+
+ public override async Task Select_null_propagation_negative3(bool async)
+ {
+ await base.Select_null_propagation_negative3(async);
+
+ AssertSql(
+"""
+SELECT `t0`.`Nickname`, CASE
+ WHEN `t0`.`Nickname` IS NOT NULL AND (`t0`.`SquadId` IS NOT NULL) THEN `t0`.`LeaderNickname` IS NOT NULL
+ ELSE NULL
+END AS `Condition`
+FROM (
+ SELECT `g`.`HasSoulPatch`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`HasSoulPatch`
+ FROM `Officers` AS `o`
+) AS `t`
+LEFT JOIN (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`, `g0`.`LeaderNickname`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`LeaderNickname`
+ FROM `Officers` AS `o0`
+) AS `t0` ON `t`.`HasSoulPatch` = TRUE
+ORDER BY `t0`.`Nickname`
+""");
+ }
+
+ public override async Task Select_null_propagation_negative4(bool async)
+ {
+ await base.Select_null_propagation_negative4(async);
+
+ AssertSql(
+"""
+SELECT `t0`.`Nickname` IS NOT NULL AND (`t0`.`SquadId` IS NOT NULL), `t0`.`Nickname`
+FROM (
+ SELECT `g`.`HasSoulPatch`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`HasSoulPatch`
+ FROM `Officers` AS `o`
+) AS `t`
+LEFT JOIN (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`
+ FROM `Officers` AS `o0`
+) AS `t0` ON `t`.`HasSoulPatch` = TRUE
+ORDER BY `t0`.`Nickname`
+""");
+ }
+
+ public override async Task Select_null_propagation_negative5(bool async)
+ {
+ await base.Select_null_propagation_negative5(async);
+
+ AssertSql(
+"""
+SELECT `t0`.`Nickname` IS NOT NULL AND (`t0`.`SquadId` IS NOT NULL), `t0`.`Nickname`
+FROM (
+ SELECT `g`.`HasSoulPatch`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`HasSoulPatch`
+ FROM `Officers` AS `o`
+) AS `t`
+LEFT JOIN (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`
+ FROM `Officers` AS `o0`
+) AS `t0` ON `t`.`HasSoulPatch` = TRUE
+ORDER BY `t0`.`Nickname`
+""");
+ }
+
+ public override async Task Select_null_propagation_negative6(bool async)
+ {
+ await base.Select_null_propagation_negative6(async);
+
+ AssertSql(
+"""
+SELECT CASE
+ WHEN `t`.`LeaderNickname` IS NOT NULL THEN CHAR_LENGTH(`t`.`LeaderNickname`) <> CHAR_LENGTH(`t`.`LeaderNickname`)
+ ELSE NULL
+END
+FROM (
+ SELECT `g`.`LeaderNickname`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`LeaderNickname`
+ FROM `Officers` AS `o`
+) AS `t`
+""");
+ }
+
+ public override async Task Select_null_propagation_negative7(bool async)
+ {
+ await base.Select_null_propagation_negative7(async);
+
+ AssertSql(
+"""
+SELECT CASE
+ WHEN `t`.`LeaderNickname` IS NOT NULL THEN TRUE
+ ELSE NULL
+END
+FROM (
+ SELECT `g`.`LeaderNickname`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`LeaderNickname`
+ FROM `Officers` AS `o`
+) AS `t`
+""");
+ }
+
+ public override async Task Select_null_propagation_negative8(bool async)
+ {
+ await base.Select_null_propagation_negative8(async);
+
+ AssertSql(
+"""
+SELECT CASE
+ WHEN `s`.`Id` IS NOT NULL THEN `c`.`Name`
+ ELSE NULL
+END
+FROM `Tags` AS `t`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearNickName` = `t0`.`Nickname`) AND (`t`.`GearSquadId` = `t0`.`SquadId`)
+LEFT JOIN `Squads` AS `s` ON `t0`.`SquadId` = `s`.`Id`
+LEFT JOIN `Cities` AS `c` ON `t0`.`AssignedCityName` = `c`.`Name`
+""");
+ }
+
+ public override async Task Select_null_propagation_negative9(bool async)
+ {
+ await base.Select_null_propagation_negative9(async);
+
+ AssertSql(
+"""
+SELECT CASE
+ WHEN `t`.`LeaderNickname` IS NOT NULL THEN COALESCE(CHAR_LENGTH(`t`.`Nickname`) = 5, FALSE)
+ ELSE NULL
+END
+FROM (
+ SELECT `g`.`Nickname`, `g`.`LeaderNickname`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`LeaderNickname`
+ FROM `Officers` AS `o`
+) AS `t`
+""");
+ }
+
+ public override async Task Select_null_propagation_works_for_navigations_with_composite_keys(bool async)
+ {
+ await base.Select_null_propagation_works_for_navigations_with_composite_keys(async);
+
+ AssertSql(
+"""
+SELECT `t0`.`Nickname`
+FROM `Tags` AS `t`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearNickName` = `t0`.`Nickname`) AND (`t`.`GearSquadId` = `t0`.`SquadId`)
+""");
+ }
+
+ public override async Task Select_null_propagation_works_for_multiple_navigations_with_composite_keys(bool async)
+ {
+ await base.Select_null_propagation_works_for_multiple_navigations_with_composite_keys(async);
+
+ AssertSql(
+"""
+SELECT CASE
+ WHEN `c`.`Name` IS NOT NULL THEN `c`.`Name`
+ ELSE NULL
+END
+FROM `Tags` AS `t`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearNickName` = `t0`.`Nickname`) AND (`t`.`GearSquadId` = `t0`.`SquadId`)
+LEFT JOIN `Tags` AS `t1` ON ((`t0`.`Nickname` = `t1`.`GearNickName`) OR (`t0`.`Nickname` IS NULL AND (`t1`.`GearNickName` IS NULL))) AND ((`t0`.`SquadId` = `t1`.`GearSquadId`) OR (`t0`.`SquadId` IS NULL AND (`t1`.`GearSquadId` IS NULL)))
+LEFT JOIN (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`, `g0`.`AssignedCityName`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`
+ FROM `Officers` AS `o0`
+) AS `t2` ON (`t1`.`GearNickName` = `t2`.`Nickname`) AND (`t1`.`GearSquadId` = `t2`.`SquadId`)
+LEFT JOIN `Cities` AS `c` ON `t2`.`AssignedCityName` = `c`.`Name`
+""");
+ }
+
+ public override async Task Select_conditional_with_anonymous_type_and_null_constant(bool async)
+ {
+ await base.Select_conditional_with_anonymous_type_and_null_constant(async);
+
+ AssertSql(
+"""
+SELECT `t`.`LeaderNickname` IS NOT NULL, `t`.`HasSoulPatch`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`
+ FROM `Officers` AS `o`
+) AS `t`
+ORDER BY `t`.`Nickname`
+""");
+ }
+
+ public override async Task Select_conditional_with_anonymous_types(bool async)
+ {
+ await base.Select_conditional_with_anonymous_types(async);
+
+ AssertSql(
+"""
+SELECT `t`.`LeaderNickname` IS NOT NULL, `t`.`Nickname`, `t`.`FullName`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`FullName`, `g`.`LeaderNickname`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`FullName`, `o`.`LeaderNickname`
+ FROM `Officers` AS `o`
+) AS `t`
+ORDER BY `t`.`Nickname`
+""");
+ }
+
+ public override async Task Where_conditional_equality_1(bool async)
+ {
+ await base.Where_conditional_equality_1(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE `t`.`LeaderNickname` IS NULL
+ORDER BY `t`.`Nickname`
+""");
+ }
+
+ public override async Task Where_conditional_equality_2(bool async)
+ {
+ await base.Where_conditional_equality_2(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE `t`.`LeaderNickname` IS NULL
+ORDER BY `t`.`Nickname`
+""");
+ }
+
+ public override async Task Where_conditional_equality_3(bool async)
+ {
+ await base.Where_conditional_equality_3(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`LeaderNickname`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`LeaderNickname`
+ FROM `Officers` AS `o`
+) AS `t`
+ORDER BY `t`.`Nickname`
+""");
+ }
+
+ public override async Task Select_coalesce_with_anonymous_types(bool async)
+ {
+ await base.Select_coalesce_with_anonymous_types(async);
+
+ AssertSql(
+"""
+SELECT `t`.`LeaderNickname`, `t`.`FullName`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`FullName`, `g`.`LeaderNickname`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`FullName`, `o`.`LeaderNickname`
+ FROM `Officers` AS `o`
+) AS `t`
+ORDER BY `t`.`Nickname`
+""");
+ }
+
+ public override async Task Where_compare_anonymous_types(bool async)
+ {
+ await base.Where_compare_anonymous_types(async);
+
+ AssertSql();
+ }
+
+ public override async Task Where_member_access_on_anonymous_type(bool async)
+ {
+ await base.Where_member_access_on_anonymous_type(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`LeaderNickname`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`LeaderNickname`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE `t`.`LeaderNickname` = 'Marcus'
+""");
+ }
+
+ public override async Task Where_compare_anonymous_types_with_uncorrelated_members(bool async)
+ {
+ await base.Where_compare_anonymous_types_with_uncorrelated_members(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`
+FROM (
+ SELECT `g`.`Nickname`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE FALSE
+""");
+ }
+
+ public override async Task Select_Where_Navigation_Scalar_Equals_Navigation_Scalar(bool async)
+ {
+ await base.Select_Where_Navigation_Scalar_Equals_Navigation_Scalar(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`GearNickName`, `t`.`GearSquadId`, `t`.`IssueDate`, `t`.`Note`, `t0`.`Id`, `t0`.`GearNickName`, `t0`.`GearSquadId`, `t0`.`IssueDate`, `t0`.`Note`
+FROM `Tags` AS `t`
+CROSS JOIN `Tags` AS `t0`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`
+ FROM `Officers` AS `o`
+) AS `t1` ON (`t`.`GearNickName` = `t1`.`Nickname`) AND (`t`.`GearSquadId` = `t1`.`SquadId`)
+LEFT JOIN (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`
+ FROM `Officers` AS `o0`
+) AS `t2` ON (`t0`.`GearNickName` = `t2`.`Nickname`) AND (`t0`.`GearSquadId` = `t2`.`SquadId`)
+WHERE (`t1`.`Nickname` = `t2`.`Nickname`) OR (`t1`.`Nickname` IS NULL AND (`t2`.`Nickname` IS NULL))
+""");
+ }
+
+ public override async Task Select_Singleton_Navigation_With_Member_Access(bool async)
+ {
+ await base.Select_Singleton_Navigation_With_Member_Access(async);
+
+ AssertSql(
+"""
+SELECT `t0`.`Nickname`, `t0`.`SquadId`, `t0`.`AssignedCityName`, `t0`.`CityOfBirthName`, `t0`.`FullName`, `t0`.`HasSoulPatch`, `t0`.`LeaderNickname`, `t0`.`LeaderSquadId`, `t0`.`Rank`, `t0`.`Discriminator`
+FROM `Tags` AS `t`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearNickName` = `t0`.`Nickname`) AND (`t`.`GearSquadId` = `t0`.`SquadId`)
+WHERE (`t0`.`Nickname` = 'Marcus') AND ((`t0`.`CityOfBirthName` <> 'Ephyra') OR `t0`.`CityOfBirthName` IS NULL)
+""");
+ }
+
+ public override async Task Select_Where_Navigation(bool async)
+ {
+ await base.Select_Where_Navigation(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`GearNickName`, `t`.`GearSquadId`, `t`.`IssueDate`, `t`.`Note`
+FROM `Tags` AS `t`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearNickName` = `t0`.`Nickname`) AND (`t`.`GearSquadId` = `t0`.`SquadId`)
+WHERE `t0`.`Nickname` = 'Marcus'
+""");
+ }
+
+ public override async Task Select_Where_Navigation_Equals_Navigation(bool async)
+ {
+ await base.Select_Where_Navigation_Equals_Navigation(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`GearNickName`, `t`.`GearSquadId`, `t`.`IssueDate`, `t`.`Note`, `t0`.`Id`, `t0`.`GearNickName`, `t0`.`GearSquadId`, `t0`.`IssueDate`, `t0`.`Note`
+FROM `Tags` AS `t`
+CROSS JOIN `Tags` AS `t0`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`
+ FROM `Officers` AS `o`
+) AS `t1` ON (`t`.`GearNickName` = `t1`.`Nickname`) AND (`t`.`GearSquadId` = `t1`.`SquadId`)
+LEFT JOIN (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`
+ FROM `Officers` AS `o0`
+) AS `t2` ON (`t0`.`GearNickName` = `t2`.`Nickname`) AND (`t0`.`GearSquadId` = `t2`.`SquadId`)
+WHERE ((`t1`.`Nickname` = `t2`.`Nickname`) OR (`t1`.`Nickname` IS NULL AND (`t2`.`Nickname` IS NULL))) AND ((`t1`.`SquadId` = `t2`.`SquadId`) OR (`t1`.`SquadId` IS NULL AND (`t2`.`SquadId` IS NULL)))
+""");
+ }
+
+ public override async Task Select_Where_Navigation_Null(bool async)
+ {
+ await base.Select_Where_Navigation_Null(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`GearNickName`, `t`.`GearSquadId`, `t`.`IssueDate`, `t`.`Note`
+FROM `Tags` AS `t`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearNickName` = `t0`.`Nickname`) AND (`t`.`GearSquadId` = `t0`.`SquadId`)
+WHERE `t0`.`Nickname` IS NULL OR (`t0`.`SquadId` IS NULL)
+""");
+ }
+
+ public override async Task Select_Where_Navigation_Null_Reverse(bool async)
+ {
+ await base.Select_Where_Navigation_Null_Reverse(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`GearNickName`, `t`.`GearSquadId`, `t`.`IssueDate`, `t`.`Note`
+FROM `Tags` AS `t`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearNickName` = `t0`.`Nickname`) AND (`t`.`GearSquadId` = `t0`.`SquadId`)
+WHERE `t0`.`Nickname` IS NULL OR (`t0`.`SquadId` IS NULL)
+""");
+ }
+
+ public override async Task Select_Where_Navigation_Scalar_Equals_Navigation_Scalar_Projected(bool async)
+ {
+ await base.Select_Where_Navigation_Scalar_Equals_Navigation_Scalar_Projected(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id` AS `Id1`, `t0`.`Id` AS `Id2`
+FROM `Tags` AS `t`
+CROSS JOIN `Tags` AS `t0`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`
+ FROM `Officers` AS `o`
+) AS `t1` ON (`t`.`GearNickName` = `t1`.`Nickname`) AND (`t`.`GearSquadId` = `t1`.`SquadId`)
+LEFT JOIN (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`
+ FROM `Officers` AS `o0`
+) AS `t2` ON (`t0`.`GearNickName` = `t2`.`Nickname`) AND (`t0`.`GearSquadId` = `t2`.`SquadId`)
+WHERE (`t1`.`Nickname` = `t2`.`Nickname`) OR (`t1`.`Nickname` IS NULL AND (`t2`.`Nickname` IS NULL))
+""");
+ }
+
+ public override async Task Optional_Navigation_Null_Coalesce_To_Clr_Type(bool async)
+ {
+ await base.Optional_Navigation_Null_Coalesce_To_Clr_Type(async);
+
+ AssertSql(
+"""
+SELECT COALESCE(`w0`.`IsAutomatic`, FALSE) AS `IsAutomatic`
+FROM `Weapons` AS `w`
+LEFT JOIN `Weapons` AS `w0` ON `w`.`SynergyWithId` = `w0`.`Id`
+ORDER BY `w`.`Id`
+LIMIT 1
+""");
+ }
+
+ public override async Task Where_subquery_boolean(bool async)
+ {
+ await base.Where_subquery_boolean(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE COALESCE((
+ SELECT `w`.`IsAutomatic`
+ FROM `Weapons` AS `w`
+ WHERE `t`.`FullName` = `w`.`OwnerFullName`
+ ORDER BY `w`.`Id`
+ LIMIT 1), FALSE)
+""");
+ }
+
+ public override async Task Where_subquery_boolean_with_pushdown(bool async)
+ {
+ await base.Where_subquery_boolean_with_pushdown(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (
+ SELECT `w`.`IsAutomatic`
+ FROM `Weapons` AS `w`
+ WHERE `t`.`FullName` = `w`.`OwnerFullName`
+ ORDER BY `w`.`Id`
+ LIMIT 1)
+""");
+ }
+
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.OuterReferenceInMultiLevelSubquery))]
+ public override async Task Where_subquery_distinct_firstordefault_boolean(bool async)
+ {
+ await base.Where_subquery_distinct_firstordefault_boolean(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (`t`.`HasSoulPatch` = TRUE) AND COALESCE((
+ SELECT `t0`.`IsAutomatic`
+ FROM (
+ SELECT DISTINCT `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+ FROM `Weapons` AS `w`
+ WHERE `t`.`FullName` = `w`.`OwnerFullName`
+ ) AS `t0`
+ ORDER BY `t0`.`Id`
+ LIMIT 1), FALSE)
+""");
+ }
+
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.OuterReferenceInMultiLevelSubquery))]
+ public override async Task Where_subquery_distinct_firstordefault_boolean_with_pushdown(bool async)
+ {
+ await base.Where_subquery_distinct_firstordefault_boolean_with_pushdown(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (`t`.`HasSoulPatch` = TRUE) AND (
+ SELECT `t0`.`IsAutomatic`
+ FROM (
+ SELECT DISTINCT `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+ FROM `Weapons` AS `w`
+ WHERE `t`.`FullName` = `w`.`OwnerFullName`
+ ) AS `t0`
+ ORDER BY `t0`.`Id`
+ LIMIT 1)
+""");
+ }
+
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.OuterReferenceInMultiLevelSubquery))]
+ public override async Task Where_subquery_distinct_first_boolean(bool async)
+ {
+ await base.Where_subquery_distinct_first_boolean(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (`t`.`HasSoulPatch` = TRUE) AND (
+ SELECT `t0`.`IsAutomatic`
+ FROM (
+ SELECT DISTINCT `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+ FROM `Weapons` AS `w`
+ WHERE `t`.`FullName` = `w`.`OwnerFullName`
+ ) AS `t0`
+ ORDER BY `t0`.`Id`
+ LIMIT 1)
+ORDER BY `t`.`Nickname`
+""");
+ }
+
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.OuterReferenceInMultiLevelSubquery))]
+ public override async Task Where_subquery_distinct_singleordefault_boolean1(bool async)
+ {
+ await base.Where_subquery_distinct_singleordefault_boolean1(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (`t`.`HasSoulPatch` = TRUE) AND COALESCE((
+ SELECT `t0`.`IsAutomatic`
+ FROM (
+ SELECT DISTINCT `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+ FROM `Weapons` AS `w`
+ WHERE (`t`.`FullName` = `w`.`OwnerFullName`) AND (`w`.`Name` LIKE '%Lancer%')
+ ) AS `t0`
+ LIMIT 1), FALSE)
+ORDER BY `t`.`Nickname`
+""");
+ }
+
+ public override async Task Where_subquery_distinct_singleordefault_boolean2(bool async)
+ {
+ await base.Where_subquery_distinct_singleordefault_boolean2(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (`t`.`HasSoulPatch` = TRUE) AND COALESCE((
+ SELECT DISTINCT `w`.`IsAutomatic`
+ FROM `Weapons` AS `w`
+ WHERE (`t`.`FullName` = `w`.`OwnerFullName`) AND (`w`.`Name` LIKE '%Lancer%')
+ LIMIT 1), FALSE)
+ORDER BY `t`.`Nickname`
+""");
+ }
+
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.OuterReferenceInMultiLevelSubquery))]
+ public override async Task Where_subquery_distinct_singleordefault_boolean_with_pushdown(bool async)
+ {
+ await base.Where_subquery_distinct_singleordefault_boolean_with_pushdown(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (`t`.`HasSoulPatch` = TRUE) AND (
+ SELECT `t0`.`IsAutomatic`
+ FROM (
+ SELECT DISTINCT `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+ FROM `Weapons` AS `w`
+ WHERE (`t`.`FullName` = `w`.`OwnerFullName`) AND (`w`.`Name` LIKE '%Lancer%')
+ ) AS `t0`
+ LIMIT 1)
+ORDER BY `t`.`Nickname`
+""");
+ }
+
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.OuterReferenceInMultiLevelSubquery))]
+ public override async Task Where_subquery_distinct_lastordefault_boolean(bool async)
+ {
+ await base.Where_subquery_distinct_lastordefault_boolean(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE NOT ((
+ SELECT `t0`.`IsAutomatic`
+ FROM (
+ SELECT DISTINCT `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+ FROM `Weapons` AS `w`
+ WHERE `t`.`FullName` = `w`.`OwnerFullName`
+ ) AS `t0`
+ ORDER BY `t0`.`Id` DESC
+ LIMIT 1))
+ORDER BY `t`.`Nickname`
+""");
+ }
+
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.OuterReferenceInMultiLevelSubquery))]
+ public override async Task Where_subquery_distinct_last_boolean(bool async)
+ {
+ await base.Where_subquery_distinct_last_boolean(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (`t`.`HasSoulPatch` = FALSE) AND (
+ SELECT `t0`.`IsAutomatic`
+ FROM (
+ SELECT DISTINCT `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+ FROM `Weapons` AS `w`
+ WHERE `t`.`FullName` = `w`.`OwnerFullName`
+ ) AS `t0`
+ ORDER BY `t0`.`Id` DESC
+ LIMIT 1)
+ORDER BY `t`.`Nickname`
+""");
+ }
+
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.OuterReferenceInMultiLevelSubquery))]
+ public override async Task Where_subquery_distinct_orderby_firstordefault_boolean(bool async)
+ {
+ await base.Where_subquery_distinct_orderby_firstordefault_boolean(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (`t`.`HasSoulPatch` = TRUE) AND COALESCE((
+ SELECT `t0`.`IsAutomatic`
+ FROM (
+ SELECT DISTINCT `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+ FROM `Weapons` AS `w`
+ WHERE `t`.`FullName` = `w`.`OwnerFullName`
+ ) AS `t0`
+ ORDER BY `t0`.`Id`
+ LIMIT 1), FALSE)
+""");
+ }
+
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.OuterReferenceInMultiLevelSubquery))]
+ public override async Task Where_subquery_distinct_orderby_firstordefault_boolean_with_pushdown(bool async)
+ {
+ await base.Where_subquery_distinct_orderby_firstordefault_boolean_with_pushdown(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (`t`.`HasSoulPatch` = TRUE) AND (
+ SELECT `t0`.`IsAutomatic`
+ FROM (
+ SELECT DISTINCT `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+ FROM `Weapons` AS `w`
+ WHERE `t`.`FullName` = `w`.`OwnerFullName`
+ ) AS `t0`
+ ORDER BY `t0`.`Id`
+ LIMIT 1)
+""");
+ }
+
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.OuterReferenceInMultiLevelSubquery))]
+ public override async Task Where_subquery_union_firstordefault_boolean(bool async)
+ {
+ await base.Where_subquery_union_firstordefault_boolean(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (`t`.`HasSoulPatch` = TRUE) AND (
+ SELECT `t0`.`IsAutomatic`
+ FROM (
+ SELECT `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+ FROM `Weapons` AS `w`
+ WHERE `t`.`FullName` = `w`.`OwnerFullName`
+ UNION
+ SELECT `w0`.`Id`, `w0`.`AmmunitionType`, `w0`.`IsAutomatic`, `w0`.`Name`, `w0`.`OwnerFullName`, `w0`.`SynergyWithId`
+ FROM `Weapons` AS `w0`
+ WHERE `t`.`FullName` = `w0`.`OwnerFullName`
+ ) AS `t0`
+ ORDER BY `t0`.`Id`
+ LIMIT 1)
+""");
+ }
+
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.OuterReferenceInMultiLevelSubquery))]
+ public override async Task Where_subquery_join_firstordefault_boolean(bool async)
+ {
+ await base.Where_subquery_join_firstordefault_boolean(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (`t`.`HasSoulPatch` = TRUE) AND (
+ SELECT `w`.`IsAutomatic`
+ FROM `Weapons` AS `w`
+ INNER JOIN (
+ SELECT `w0`.`Id`, `w0`.`AmmunitionType`, `w0`.`IsAutomatic`, `w0`.`Name`, `w0`.`OwnerFullName`, `w0`.`SynergyWithId`
+ FROM `Weapons` AS `w0`
+ WHERE `t`.`FullName` = `w0`.`OwnerFullName`
+ ) AS `t0` ON `w`.`Id` = `t0`.`Id`
+ WHERE `t`.`FullName` = `w`.`OwnerFullName`
+ ORDER BY `w`.`Id`
+ LIMIT 1)
+""");
+ }
+
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.OuterReferenceInMultiLevelSubquery))]
+ public override async Task Where_subquery_left_join_firstordefault_boolean(bool async)
+ {
+ await base.Where_subquery_left_join_firstordefault_boolean(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (`t`.`HasSoulPatch` = TRUE) AND (
+ SELECT `w`.`IsAutomatic`
+ FROM `Weapons` AS `w`
+ LEFT JOIN (
+ SELECT `w0`.`Id`, `w0`.`AmmunitionType`, `w0`.`IsAutomatic`, `w0`.`Name`, `w0`.`OwnerFullName`, `w0`.`SynergyWithId`
+ FROM `Weapons` AS `w0`
+ WHERE `t`.`FullName` = `w0`.`OwnerFullName`
+ ) AS `t0` ON `w`.`Id` = `t0`.`Id`
+ WHERE `t`.`FullName` = `w`.`OwnerFullName`
+ ORDER BY `w`.`Id`
+ LIMIT 1)
+""");
+ }
+
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.OuterReferenceInMultiLevelSubquery))]
+ public override async Task Where_subquery_concat_firstordefault_boolean(bool async)
+ {
+ await base.Where_subquery_concat_firstordefault_boolean(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE (`t`.`HasSoulPatch` = TRUE) AND (
+ SELECT `t0`.`IsAutomatic`
+ FROM (
+ SELECT `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+ FROM `Weapons` AS `w`
+ WHERE `t`.`FullName` = `w`.`OwnerFullName`
+ UNION ALL
+ SELECT `w0`.`Id`, `w0`.`AmmunitionType`, `w0`.`IsAutomatic`, `w0`.`Name`, `w0`.`OwnerFullName`, `w0`.`SynergyWithId`
+ FROM `Weapons` AS `w0`
+ WHERE `t`.`FullName` = `w0`.`OwnerFullName`
+ ) AS `t0`
+ ORDER BY `t0`.`Id`
+ LIMIT 1)
+""");
+ }
+
+ public override async Task Concat_with_count(bool async)
+ {
+ await base.Concat_with_count(async);
+
+ AssertSql(
+"""
+SELECT COUNT(*)
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+ UNION ALL
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`, `g0`.`AssignedCityName`, `g0`.`CityOfBirthName`, `g0`.`FullName`, `g0`.`HasSoulPatch`, `g0`.`LeaderNickname`, `g0`.`LeaderSquadId`, `g0`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o0`
+) AS `t0`
+""");
+ }
+
+ public override async Task Concat_scalars_with_count(bool async)
+ {
+ await base.Concat_scalars_with_count(async);
+
+ AssertSql(
+"""
+SELECT COUNT(*)
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+ UNION ALL
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`, `g0`.`AssignedCityName`, `g0`.`CityOfBirthName`, `g0`.`FullName`, `g0`.`HasSoulPatch`, `g0`.`LeaderNickname`, `g0`.`LeaderSquadId`, `g0`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o0`
+) AS `t0`
+""");
+ }
+
+ public override async Task Concat_anonymous_with_count(bool async)
+ {
+ await base.Concat_anonymous_with_count(async);
+
+ AssertSql(
+"""
+SELECT COUNT(*)
+FROM (
+ SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`, `t`.`Nickname` AS `Name`
+ FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+ ) AS `t`
+ UNION ALL
+ SELECT `t1`.`Nickname`, `t1`.`SquadId`, `t1`.`AssignedCityName`, `t1`.`CityOfBirthName`, `t1`.`FullName`, `t1`.`HasSoulPatch`, `t1`.`LeaderNickname`, `t1`.`LeaderSquadId`, `t1`.`Rank`, `t1`.`Discriminator`, `t1`.`FullName` AS `Name`
+ FROM (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`, `g0`.`AssignedCityName`, `g0`.`CityOfBirthName`, `g0`.`FullName`, `g0`.`HasSoulPatch`, `g0`.`LeaderNickname`, `g0`.`LeaderSquadId`, `g0`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o0`
+ ) AS `t1`
+) AS `t0`
+""");
+ }
+
+ public override async Task Concat_with_scalar_projection(bool async)
+ {
+ await base.Concat_with_scalar_projection(async);
+
+ AssertSql(
+"""
+SELECT `g`.`Nickname`
+FROM `Gears` AS `g`
+UNION ALL
+SELECT `o`.`Nickname`
+FROM `Officers` AS `o`
+UNION ALL
+SELECT `g0`.`Nickname`
+FROM `Gears` AS `g0`
+UNION ALL
+SELECT `o0`.`Nickname`
+FROM `Officers` AS `o0`
+""");
+ }
+
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.OuterReferenceInMultiLevelSubquery))]
+ public override async Task Select_navigation_with_concat_and_count(bool async)
+ {
+ await base.Select_navigation_with_concat_and_count(async);
+
+ AssertSql(
+"""
+SELECT (
+ SELECT COUNT(*)
+ FROM (
+ SELECT `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+ FROM `Weapons` AS `w`
+ WHERE `t`.`FullName` = `w`.`OwnerFullName`
+ UNION ALL
+ SELECT `w0`.`Id`, `w0`.`AmmunitionType`, `w0`.`IsAutomatic`, `w0`.`Name`, `w0`.`OwnerFullName`, `w0`.`SynergyWithId`
+ FROM `Weapons` AS `w0`
+ WHERE `t`.`FullName` = `w0`.`OwnerFullName`
+ ) AS `t0`)
+FROM (
+ SELECT `g`.`FullName`, `g`.`HasSoulPatch`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`FullName`, `o`.`HasSoulPatch`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE `t`.`HasSoulPatch` = FALSE
+""");
+ }
+
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.OuterReferenceInMultiLevelSubquery))]
+ public override async Task Concat_with_collection_navigations(bool async)
+ {
+ await base.Concat_with_collection_navigations(async);
+
+ AssertSql(
+"""
+SELECT (
+ SELECT COUNT(*)
+ FROM (
+ SELECT `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+ FROM `Weapons` AS `w`
+ WHERE `t`.`FullName` = `w`.`OwnerFullName`
+ UNION
+ SELECT `w0`.`Id`, `w0`.`AmmunitionType`, `w0`.`IsAutomatic`, `w0`.`Name`, `w0`.`OwnerFullName`, `w0`.`SynergyWithId`
+ FROM `Weapons` AS `w0`
+ WHERE `t`.`FullName` = `w0`.`OwnerFullName`
+ ) AS `t0`)
+FROM (
+ SELECT `g`.`FullName`, `g`.`HasSoulPatch`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`FullName`, `o`.`HasSoulPatch`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE `t`.`HasSoulPatch` = TRUE
+""");
+ }
+
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.OuterReferenceInMultiLevelSubquery))]
+ public override async Task Union_with_collection_navigations(bool async)
+ {
+ await base.Union_with_collection_navigations(async);
+
+ AssertSql(
+"""
+SELECT (
+ SELECT COUNT(*)
+ FROM (
+ SELECT `t1`.`Nickname`, `t1`.`SquadId`, `t1`.`AssignedCityName`, `t1`.`CityOfBirthName`, `t1`.`FullName`, `t1`.`HasSoulPatch`, `t1`.`LeaderNickname`, `t1`.`LeaderSquadId`, `t1`.`Rank`, `t1`.`Discriminator`
+ FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o0`
+ ) AS `t1`
+ WHERE (`t`.`Nickname` = `t1`.`LeaderNickname`) AND (`t`.`SquadId` = `t1`.`LeaderSquadId`)
+ UNION
+ SELECT `t2`.`Nickname`, `t2`.`SquadId`, `t2`.`AssignedCityName`, `t2`.`CityOfBirthName`, `t2`.`FullName`, `t2`.`HasSoulPatch`, `t2`.`LeaderNickname`, `t2`.`LeaderSquadId`, `t2`.`Rank`, `t2`.`Discriminator`
+ FROM (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`, `g0`.`AssignedCityName`, `g0`.`CityOfBirthName`, `g0`.`FullName`, `g0`.`HasSoulPatch`, `g0`.`LeaderNickname`, `g0`.`LeaderSquadId`, `g0`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o1`.`Nickname`, `o1`.`SquadId`, `o1`.`AssignedCityName`, `o1`.`CityOfBirthName`, `o1`.`FullName`, `o1`.`HasSoulPatch`, `o1`.`LeaderNickname`, `o1`.`LeaderSquadId`, `o1`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o1`
+ ) AS `t2`
+ WHERE (`t`.`Nickname` = `t2`.`LeaderNickname`) AND (`t`.`SquadId` = `t2`.`LeaderSquadId`)
+ ) AS `t0`)
+FROM (
+ SELECT `o`.`Nickname`, `o`.`SquadId`
+ FROM `Officers` AS `o`
+) AS `t`
+""");
+ }
+
+ [SupportedServerVersionCondition(nameof(ServerVersionSupport.OuterReferenceInMultiLevelSubquery))]
+ public override async Task Select_subquery_distinct_firstordefault(bool async)
+ {
+ await base.Select_subquery_distinct_firstordefault(async);
+
+ AssertSql(
+"""
+SELECT (
+ SELECT `t0`.`Name`
+ FROM (
+ SELECT DISTINCT `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+ FROM `Weapons` AS `w`
+ WHERE `t`.`FullName` = `w`.`OwnerFullName`
+ ) AS `t0`
+ ORDER BY `t0`.`Id`
+ LIMIT 1)
+FROM (
+ SELECT `g`.`FullName`, `g`.`HasSoulPatch`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`FullName`, `o`.`HasSoulPatch`
+ FROM `Officers` AS `o`
+) AS `t`
+WHERE `t`.`HasSoulPatch` = TRUE
+""");
+ }
+
+ public override async Task Singleton_Navigation_With_Member_Access(bool async)
+ {
+ await base.Singleton_Navigation_With_Member_Access(async);
+
+ AssertSql(
+"""
+SELECT `t0`.`CityOfBirthName` AS `B`
+FROM `Tags` AS `t`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`CityOfBirthName`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`CityOfBirthName`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearNickName` = `t0`.`Nickname`) AND (`t`.`GearSquadId` = `t0`.`SquadId`)
+WHERE (`t0`.`Nickname` = 'Marcus') AND ((`t0`.`CityOfBirthName` <> 'Ephyra') OR `t0`.`CityOfBirthName` IS NULL)
+""");
+ }
+
+ public override async Task GroupJoin_Composite_Key(bool async)
+ {
+ await base.GroupJoin_Composite_Key(async);
+
+ AssertSql(
+"""
+SELECT `t0`.`Nickname`, `t0`.`SquadId`, `t0`.`AssignedCityName`, `t0`.`CityOfBirthName`, `t0`.`FullName`, `t0`.`HasSoulPatch`, `t0`.`LeaderNickname`, `t0`.`LeaderSquadId`, `t0`.`Rank`, `t0`.`Discriminator`
+FROM `Tags` AS `t`
+INNER JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearNickName` = `t0`.`Nickname`) AND (`t`.`GearSquadId` = `t0`.`SquadId`)
+""");
+ }
+
+ public override async Task Join_navigation_translated_to_subquery_composite_key(bool async)
+ {
+ await base.Join_navigation_translated_to_subquery_composite_key(async);
+
+ AssertSql(
+"""
+SELECT `t`.`FullName`, `t1`.`Note`
+FROM (
+ SELECT `g`.`FullName`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`FullName`
+ FROM `Officers` AS `o`
+) AS `t`
+INNER JOIN (
+ SELECT `t0`.`Note`, `t2`.`FullName`
+ FROM `Tags` AS `t0`
+ LEFT JOIN (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`, `g0`.`FullName`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`FullName`
+ FROM `Officers` AS `o0`
+ ) AS `t2` ON (`t0`.`GearNickName` = `t2`.`Nickname`) AND (`t0`.`GearSquadId` = `t2`.`SquadId`)
+) AS `t1` ON `t`.`FullName` = `t1`.`FullName`
+""");
+ }
+
+ public override async Task Join_with_order_by_on_inner_sequence_navigation_translated_to_subquery_composite_key(bool async)
+ {
+ await base.Join_with_order_by_on_inner_sequence_navigation_translated_to_subquery_composite_key(async);
+
+ AssertSql(
+"""
+SELECT `t`.`FullName`, `t1`.`Note`
+FROM (
+ SELECT `g`.`FullName`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`FullName`
+ FROM `Officers` AS `o`
+) AS `t`
+INNER JOIN (
+ SELECT `t0`.`Note`, `t2`.`FullName`
+ FROM `Tags` AS `t0`
+ LEFT JOIN (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`, `g0`.`FullName`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`FullName`
+ FROM `Officers` AS `o0`
+ ) AS `t2` ON (`t0`.`GearNickName` = `t2`.`Nickname`) AND (`t0`.`GearSquadId` = `t2`.`SquadId`)
+) AS `t1` ON `t`.`FullName` = `t1`.`FullName`
+""");
+ }
+
+ public override async Task Join_with_order_by_without_skip_or_take(bool async)
+ {
+ await base.Join_with_order_by_without_skip_or_take(async);
+
+ AssertSql(
+"""
+SELECT `w`.`Name`, `t`.`FullName`
+FROM (
+ SELECT `g`.`FullName`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`FullName`
+ FROM `Officers` AS `o`
+) AS `t`
+INNER JOIN `Weapons` AS `w` ON `t`.`FullName` = `w`.`OwnerFullName`
+""");
+ }
+
+ public override async Task Join_with_order_by_without_skip_or_take_nested(bool async)
+ {
+ await base.Join_with_order_by_without_skip_or_take_nested(async);
+
+ AssertSql(
+"""
+SELECT `w`.`Name`, `t`.`FullName`
+FROM `Squads` AS `s`
+INNER JOIN (
+ SELECT `g`.`SquadId`, `g`.`FullName`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`SquadId`, `o`.`FullName`
+ FROM `Officers` AS `o`
+) AS `t` ON `s`.`Id` = `t`.`SquadId`
+INNER JOIN `Weapons` AS `w` ON `t`.`FullName` = `w`.`OwnerFullName`
+""");
+ }
+
+ public override async Task Collection_with_inheritance_and_join_include_joined(bool async)
+ {
+ await base.Collection_with_inheritance_and_join_include_joined(async);
+
+ AssertSql(
+"""
+SELECT `t0`.`Nickname`, `t0`.`SquadId`, `t0`.`AssignedCityName`, `t0`.`CityOfBirthName`, `t0`.`FullName`, `t0`.`HasSoulPatch`, `t0`.`LeaderNickname`, `t0`.`LeaderSquadId`, `t0`.`Rank`, `t0`.`Discriminator`, `t1`.`Id`, `t1`.`GearNickName`, `t1`.`GearSquadId`, `t1`.`IssueDate`, `t1`.`Note`
+FROM `Tags` AS `t`
+INNER JOIN (
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearSquadId` = `t0`.`SquadId`) AND (`t`.`GearNickName` = `t0`.`Nickname`)
+LEFT JOIN `Tags` AS `t1` ON (`t0`.`Nickname` = `t1`.`GearNickName`) AND (`t0`.`SquadId` = `t1`.`GearSquadId`)
+""");
+ }
+
+ public override async Task Collection_with_inheritance_and_join_include_source(bool async)
+ {
+ await base.Collection_with_inheritance_and_join_include_source(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`, `t1`.`Id`, `t1`.`GearNickName`, `t1`.`GearSquadId`, `t1`.`IssueDate`, `t1`.`Note`
+FROM (
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+INNER JOIN `Tags` AS `t0` ON (`t`.`SquadId` = `t0`.`GearSquadId`) AND (`t`.`Nickname` = `t0`.`GearNickName`)
+LEFT JOIN `Tags` AS `t1` ON (`t`.`Nickname` = `t1`.`GearNickName`) AND (`t`.`SquadId` = `t1`.`GearSquadId`)
+""");
+ }
+
+ public override async Task Non_unicode_string_literal_is_used_for_non_unicode_column(bool async)
+ {
+ await base.Non_unicode_string_literal_is_used_for_non_unicode_column(async);
+
+ AssertSql(
+"""
+SELECT `c`.`Name`, `c`.`Location`, `c`.`Nation`
+FROM `Cities` AS `c`
+WHERE `c`.`Location` = 'Unknown'
+""");
+ }
+
+ public override async Task Non_unicode_string_literal_is_used_for_non_unicode_column_right(bool async)
+ {
+ await base.Non_unicode_string_literal_is_used_for_non_unicode_column_right(async);
+
+ AssertSql(
+"""
+SELECT `c`.`Name`, `c`.`Location`, `c`.`Nation`
+FROM `Cities` AS `c`
+WHERE 'Unknown' = `c`.`Location`
+""");
+ }
+
+ public override async Task Non_unicode_parameter_is_used_for_non_unicode_column(bool async)
+ {
+ await base.Non_unicode_parameter_is_used_for_non_unicode_column(async);
+
+ AssertSql(
+"""
+@__value_0='Unknown' (Size = 4000)
+
+SELECT `c`.`Name`, `c`.`Location`, `c`.`Nation`
+FROM `Cities` AS `c`
+WHERE `c`.`Location` = @__value_0
+""");
+ }
+
+ public override async Task Non_unicode_string_literals_in_contains_is_used_for_non_unicode_column(bool async)
+ {
+ await base.Non_unicode_string_literals_in_contains_is_used_for_non_unicode_column(async);
+
+ AssertSql(
+"""
+SELECT `c`.`Name`, `c`.`Location`, `c`.`Nation`
+FROM `Cities` AS `c`
+WHERE `c`.`Location` IN ('Unknown', 'Jacinto''s location', 'Ephyra''s location')
+""");
+ }
+
+ public override async Task Non_unicode_string_literals_is_used_for_non_unicode_column_with_subquery(bool async)
+ {
+ await base.Non_unicode_string_literals_is_used_for_non_unicode_column_with_subquery(async);
+
+ AssertSql(
+"""
+SELECT `c`.`Name`, `c`.`Location`, `c`.`Nation`
+FROM `Cities` AS `c`
+WHERE (`c`.`Location` = 'Unknown') AND ((
+ SELECT COUNT(*)
+ FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+ ) AS `t`
+ WHERE (`c`.`Name` = `t`.`CityOfBirthName`) AND (`t`.`Nickname` = 'Paduk')) = 1)
+""");
+ }
+
+ public override async Task Non_unicode_string_literals_is_used_for_non_unicode_column_in_subquery(bool async)
+ {
+ await base.Non_unicode_string_literals_is_used_for_non_unicode_column_in_subquery(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+INNER JOIN `Cities` AS `c` ON `t`.`CityOfBirthName` = `c`.`Name`
+WHERE (`t`.`Nickname` = 'Marcus') AND (`c`.`Location` = 'Jacinto''s location')
+""");
+ }
+
+ public override async Task Non_unicode_string_literals_is_used_for_non_unicode_column_with_contains(bool async)
+ {
+ await base.Non_unicode_string_literals_is_used_for_non_unicode_column_with_contains(async);
+
+ AssertSql(
+"""
+SELECT `c`.`Name`, `c`.`Location`, `c`.`Nation`
+FROM `Cities` AS `c`
+WHERE `c`.`Location` LIKE '%Jacinto%'
+""");
+ }
+
+ public override async Task Non_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);
+
+ AssertSql(
+"""
+SELECT `c`.`Name`, `c`.`Location`, `c`.`Nation`
+FROM `Cities` AS `c`
+WHERE CONCAT(COALESCE(`c`.`Location`, ''), 'Added') LIKE '%Add%'
+""");
+ }
+
+ public override void Include_on_GroupJoin_SelectMany_DefaultIfEmpty_with_coalesce_result1()
+ {
+ base.Include_on_GroupJoin_SelectMany_DefaultIfEmpty_with_coalesce_result1();
+
+ // Issue#16897
+ AssertSql(
+"""
+SELECT `t0`.`Nickname`, `t0`.`SquadId`, `t0`.`AssignedCityName`, `t0`.`CityOfBirthName`, `t0`.`FullName`, `t0`.`HasSoulPatch`, `t0`.`LeaderNickname`, `t0`.`LeaderSquadId`, `t0`.`Rank`, `t0`.`Discriminator`, `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`, `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+LEFT JOIN (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`, `g0`.`AssignedCityName`, `g0`.`CityOfBirthName`, `g0`.`FullName`, `g0`.`HasSoulPatch`, `g0`.`LeaderNickname`, `g0`.`LeaderSquadId`, `g0`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o0`
+) AS `t0` ON `t`.`LeaderNickname` = `t0`.`Nickname`
+LEFT JOIN `Weapons` AS `w` ON `t`.`FullName` = `w`.`OwnerFullName`
+ORDER BY `t`.`Nickname`, `t`.`SquadId`, `t0`.`Nickname`, `t0`.`SquadId`
+""");
+ }
+
+ public override void Include_on_GroupJoin_SelectMany_DefaultIfEmpty_with_coalesce_result2()
+ {
+ base.Include_on_GroupJoin_SelectMany_DefaultIfEmpty_with_coalesce_result2();
+
+ // Issue#16897
+ AssertSql(
+"""
+SELECT `t0`.`Nickname`, `t0`.`SquadId`, `t0`.`AssignedCityName`, `t0`.`CityOfBirthName`, `t0`.`FullName`, `t0`.`HasSoulPatch`, `t0`.`LeaderNickname`, `t0`.`LeaderSquadId`, `t0`.`Rank`, `t0`.`Discriminator`, `t`.`Nickname`, `t`.`SquadId`, `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+LEFT JOIN (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`, `g0`.`AssignedCityName`, `g0`.`CityOfBirthName`, `g0`.`FullName`, `g0`.`HasSoulPatch`, `g0`.`LeaderNickname`, `g0`.`LeaderSquadId`, `g0`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o0`
+) AS `t0` ON `t`.`LeaderNickname` = `t0`.`Nickname`
+LEFT JOIN `Weapons` AS `w` ON `t0`.`FullName` = `w`.`OwnerFullName`
+ORDER BY `t`.`Nickname`, `t`.`SquadId`, `t0`.`Nickname`, `t0`.`SquadId`
+""");
+ }
+
+ public override async Task Include_on_GroupJoin_SelectMany_DefaultIfEmpty_with_coalesce_result3(bool async)
+ {
+ await base.Include_on_GroupJoin_SelectMany_DefaultIfEmpty_with_coalesce_result3(async);
+
+ // Issue#16897
+ AssertSql(
+"""
+SELECT `t0`.`Nickname`, `t0`.`SquadId`, `t0`.`AssignedCityName`, `t0`.`CityOfBirthName`, `t0`.`FullName`, `t0`.`HasSoulPatch`, `t0`.`LeaderNickname`, `t0`.`LeaderSquadId`, `t0`.`Rank`, `t0`.`Discriminator`, `t`.`Nickname`, `t`.`SquadId`, `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`, `w0`.`Id`, `w0`.`AmmunitionType`, `w0`.`IsAutomatic`, `w0`.`Name`, `w0`.`OwnerFullName`, `w0`.`SynergyWithId`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+LEFT JOIN (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`, `g0`.`AssignedCityName`, `g0`.`CityOfBirthName`, `g0`.`FullName`, `g0`.`HasSoulPatch`, `g0`.`LeaderNickname`, `g0`.`LeaderSquadId`, `g0`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o0`
+) AS `t0` ON `t`.`LeaderNickname` = `t0`.`Nickname`
+LEFT JOIN `Weapons` AS `w` ON `t0`.`FullName` = `w`.`OwnerFullName`
+LEFT JOIN `Weapons` AS `w0` ON `t`.`FullName` = `w0`.`OwnerFullName`
+ORDER BY `t`.`Nickname`, `t`.`SquadId`, `t0`.`Nickname`, `t0`.`SquadId`, `w`.`Id`
+""");
+ }
+
+ public override async Task Include_on_GroupJoin_SelectMany_DefaultIfEmpty_with_coalesce_result4(bool async)
+ {
+ await base.Include_on_GroupJoin_SelectMany_DefaultIfEmpty_with_coalesce_result4(async);
+
+ // Issue#16897
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`, `t0`.`Nickname`, `t0`.`SquadId`, `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`, `t0`.`AssignedCityName`, `t0`.`CityOfBirthName`, `t0`.`FullName`, `t0`.`HasSoulPatch`, `t0`.`LeaderNickname`, `t0`.`LeaderSquadId`, `t0`.`Rank`, `t0`.`Discriminator`, `w0`.`Id`, `w0`.`AmmunitionType`, `w0`.`IsAutomatic`, `w0`.`Name`, `w0`.`OwnerFullName`, `w0`.`SynergyWithId`, `w1`.`Id`, `w1`.`AmmunitionType`, `w1`.`IsAutomatic`, `w1`.`Name`, `w1`.`OwnerFullName`, `w1`.`SynergyWithId`, `w2`.`Id`, `w2`.`AmmunitionType`, `w2`.`IsAutomatic`, `w2`.`Name`, `w2`.`OwnerFullName`, `w2`.`SynergyWithId`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+LEFT JOIN (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`, `g0`.`AssignedCityName`, `g0`.`CityOfBirthName`, `g0`.`FullName`, `g0`.`HasSoulPatch`, `g0`.`LeaderNickname`, `g0`.`LeaderSquadId`, `g0`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o0`
+) AS `t0` ON `t`.`LeaderNickname` = `t0`.`Nickname`
+LEFT JOIN `Weapons` AS `w` ON `t`.`FullName` = `w`.`OwnerFullName`
+LEFT JOIN `Weapons` AS `w0` ON `t0`.`FullName` = `w0`.`OwnerFullName`
+LEFT JOIN `Weapons` AS `w1` ON `t0`.`FullName` = `w1`.`OwnerFullName`
+LEFT JOIN `Weapons` AS `w2` ON `t`.`FullName` = `w2`.`OwnerFullName`
+ORDER BY `t`.`Nickname`, `t`.`SquadId`, `t0`.`Nickname`, `t0`.`SquadId`, `w`.`Id`, `w0`.`Id`, `w1`.`Id`
+""");
+ }
+
+ public override async Task Include_on_GroupJoin_SelectMany_DefaultIfEmpty_with_inheritance_and_coalesce_result(bool async)
+ {
+ await base.Include_on_GroupJoin_SelectMany_DefaultIfEmpty_with_inheritance_and_coalesce_result(async);
+
+ // Issue#16897
+ AssertSql(
+$"""
+SELECT `t0`.`Nickname`, `t0`.`SquadId`, `t0`.`AssignedCityName`, `t0`.`CityOfBirthName`, `t0`.`FullName`, `t0`.`HasSoulPatch`, `t0`.`LeaderNickname`, `t0`.`LeaderSquadId`, `t0`.`Rank`, `t0`.`Discriminator`, `t`.`Nickname`, `t`.`SquadId`, `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`, `w0`.`Id`, `w0`.`AmmunitionType`, `w0`.`IsAutomatic`, `w0`.`Name`, `w0`.`OwnerFullName`, `w0`.`SynergyWithId`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+LEFT JOIN (
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, {MySqlTestHelpers.MySqlBug96947Workaround(@"'Officer'")} AS `Discriminator`
+ FROM `Officers` AS `o0`
+) AS `t0` ON `t`.`LeaderNickname` = `t0`.`Nickname`
+LEFT JOIN `Weapons` AS `w` ON `t0`.`FullName` = `w`.`OwnerFullName`
+LEFT JOIN `Weapons` AS `w0` ON `t`.`FullName` = `w0`.`OwnerFullName`
+ORDER BY `t`.`Nickname`, `t`.`SquadId`, `t0`.`Nickname`, `t0`.`SquadId`, `w`.`Id`
+""");
+ }
+
+ public override async Task Include_on_GroupJoin_SelectMany_DefaultIfEmpty_with_conditional_result(bool async)
+ {
+ await base.Include_on_GroupJoin_SelectMany_DefaultIfEmpty_with_conditional_result(async);
+
+ // Issue#16897
+ AssertSql(
+"""
+SELECT `t0`.`Nickname` IS NOT NULL AND (`t0`.`SquadId` IS NOT NULL), `t0`.`Nickname`, `t0`.`SquadId`, `t0`.`AssignedCityName`, `t0`.`CityOfBirthName`, `t0`.`FullName`, `t0`.`HasSoulPatch`, `t0`.`LeaderNickname`, `t0`.`LeaderSquadId`, `t0`.`Rank`, `t0`.`Discriminator`, `t`.`Nickname`, `t`.`SquadId`, `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`, `w0`.`Id`, `w0`.`AmmunitionType`, `w0`.`IsAutomatic`, `w0`.`Name`, `w0`.`OwnerFullName`, `w0`.`SynergyWithId`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+LEFT JOIN (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`, `g0`.`AssignedCityName`, `g0`.`CityOfBirthName`, `g0`.`FullName`, `g0`.`HasSoulPatch`, `g0`.`LeaderNickname`, `g0`.`LeaderSquadId`, `g0`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o0`
+) AS `t0` ON `t`.`LeaderNickname` = `t0`.`Nickname`
+LEFT JOIN `Weapons` AS `w` ON `t0`.`FullName` = `w`.`OwnerFullName`
+LEFT JOIN `Weapons` AS `w0` ON `t`.`FullName` = `w0`.`OwnerFullName`
+ORDER BY `t`.`Nickname`, `t`.`SquadId`, `t0`.`Nickname`, `t0`.`SquadId`, `w`.`Id`
+""");
+ }
+
+ public override async Task Include_on_GroupJoin_SelectMany_DefaultIfEmpty_with_complex_projection_result(bool async)
+ {
+ await base.Include_on_GroupJoin_SelectMany_DefaultIfEmpty_with_complex_projection_result(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`, `t0`.`Nickname`, `t0`.`SquadId`, `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`, `t0`.`AssignedCityName`, `t0`.`CityOfBirthName`, `t0`.`FullName`, `t0`.`HasSoulPatch`, `t0`.`LeaderNickname`, `t0`.`LeaderSquadId`, `t0`.`Rank`, `t0`.`Discriminator`, `w0`.`Id`, `w0`.`AmmunitionType`, `w0`.`IsAutomatic`, `w0`.`Name`, `w0`.`OwnerFullName`, `w0`.`SynergyWithId`, `w1`.`Id`, `w1`.`AmmunitionType`, `w1`.`IsAutomatic`, `w1`.`Name`, `w1`.`OwnerFullName`, `w1`.`SynergyWithId`, `w2`.`Id`, `w2`.`AmmunitionType`, `w2`.`IsAutomatic`, `w2`.`Name`, `w2`.`OwnerFullName`, `w2`.`SynergyWithId`, `t0`.`Nickname` IS NOT NULL AND (`t0`.`SquadId` IS NOT NULL), `w3`.`Id`, `w3`.`AmmunitionType`, `w3`.`IsAutomatic`, `w3`.`Name`, `w3`.`OwnerFullName`, `w3`.`SynergyWithId`, `w4`.`Id`, `w4`.`AmmunitionType`, `w4`.`IsAutomatic`, `w4`.`Name`, `w4`.`OwnerFullName`, `w4`.`SynergyWithId`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+LEFT JOIN (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`, `g0`.`AssignedCityName`, `g0`.`CityOfBirthName`, `g0`.`FullName`, `g0`.`HasSoulPatch`, `g0`.`LeaderNickname`, `g0`.`LeaderSquadId`, `g0`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o0`
+) AS `t0` ON `t`.`LeaderNickname` = `t0`.`Nickname`
+LEFT JOIN `Weapons` AS `w` ON `t`.`FullName` = `w`.`OwnerFullName`
+LEFT JOIN `Weapons` AS `w0` ON `t0`.`FullName` = `w0`.`OwnerFullName`
+LEFT JOIN `Weapons` AS `w1` ON `t0`.`FullName` = `w1`.`OwnerFullName`
+LEFT JOIN `Weapons` AS `w2` ON `t`.`FullName` = `w2`.`OwnerFullName`
+LEFT JOIN `Weapons` AS `w3` ON `t0`.`FullName` = `w3`.`OwnerFullName`
+LEFT JOIN `Weapons` AS `w4` ON `t`.`FullName` = `w4`.`OwnerFullName`
+ORDER BY `t`.`Nickname`, `t`.`SquadId`, `t0`.`Nickname`, `t0`.`SquadId`, `w`.`Id`, `w0`.`Id`, `w1`.`Id`, `w2`.`Id`, `w3`.`Id`
+""");
+ }
+
+ public override async Task Coalesce_operator_in_predicate(bool async)
+ {
+ await base.Coalesce_operator_in_predicate(async);
+
+ AssertSql(
+"""
+SELECT `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+FROM `Weapons` AS `w`
+WHERE COALESCE(`w`.`IsAutomatic`, FALSE)
+""");
+ }
+
+ public override async Task Coalesce_operator_in_predicate_with_other_conditions(bool async)
+ {
+ await base.Coalesce_operator_in_predicate_with_other_conditions(async);
+
+ AssertSql(
+"""
+SELECT `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+FROM `Weapons` AS `w`
+WHERE (`w`.`AmmunitionType` = 1) AND COALESCE(`w`.`IsAutomatic`, FALSE)
+""");
+ }
+
+ public override async Task Coalesce_operator_in_projection_with_other_conditions(bool async)
+ {
+ await base.Coalesce_operator_in_projection_with_other_conditions(async);
+
+ AssertSql(
+"""
+SELECT ((`w`.`AmmunitionType` = 1) AND `w`.`AmmunitionType` IS NOT NULL) AND COALESCE(`w`.`IsAutomatic`, FALSE)
+FROM `Weapons` AS `w`
+""");
+ }
+
+ public override async Task Optional_navigation_type_compensation_works_with_predicate(bool async)
+ {
+ await base.Optional_navigation_type_compensation_works_with_predicate(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`GearNickName`, `t`.`GearSquadId`, `t`.`IssueDate`, `t`.`Note`
+FROM `Tags` AS `t`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`HasSoulPatch`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`HasSoulPatch`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearNickName` = `t0`.`Nickname`) AND (`t`.`GearSquadId` = `t0`.`SquadId`)
+WHERE ((`t`.`Note` <> 'K.I.A.') OR `t`.`Note` IS NULL) AND (`t0`.`HasSoulPatch` = TRUE)
+""");
+ }
+
+ public override async Task Optional_navigation_type_compensation_works_with_predicate2(bool async)
+ {
+ await base.Optional_navigation_type_compensation_works_with_predicate2(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`GearNickName`, `t`.`GearSquadId`, `t`.`IssueDate`, `t`.`Note`
+FROM `Tags` AS `t`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`HasSoulPatch`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`HasSoulPatch`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearNickName` = `t0`.`Nickname`) AND (`t`.`GearSquadId` = `t0`.`SquadId`)
+WHERE `t0`.`HasSoulPatch` = TRUE
+""");
+ }
+
+ public override async Task Optional_navigation_type_compensation_works_with_predicate_negated(bool async)
+ {
+ await base.Optional_navigation_type_compensation_works_with_predicate_negated(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`GearNickName`, `t`.`GearSquadId`, `t`.`IssueDate`, `t`.`Note`
+FROM `Tags` AS `t`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`HasSoulPatch`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`HasSoulPatch`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearNickName` = `t0`.`Nickname`) AND (`t`.`GearSquadId` = `t0`.`SquadId`)
+WHERE `t0`.`HasSoulPatch` = FALSE
+""");
+ }
+
+ public override async Task Optional_navigation_type_compensation_works_with_predicate_negated_complex1(bool async)
+ {
+ await base.Optional_navigation_type_compensation_works_with_predicate_negated_complex1(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`GearNickName`, `t`.`GearSquadId`, `t`.`IssueDate`, `t`.`Note`
+FROM `Tags` AS `t`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`HasSoulPatch`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`HasSoulPatch`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearNickName` = `t0`.`Nickname`) AND (`t`.`GearSquadId` = `t0`.`SquadId`)
+WHERE NOT (CASE
+ WHEN `t0`.`HasSoulPatch` = TRUE THEN TRUE
+ ELSE `t0`.`HasSoulPatch`
+END)
+""");
+ }
+
+ public override async Task Optional_navigation_type_compensation_works_with_predicate_negated_complex2(bool async)
+ {
+ await base.Optional_navigation_type_compensation_works_with_predicate_negated_complex2(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`GearNickName`, `t`.`GearSquadId`, `t`.`IssueDate`, `t`.`Note`
+FROM `Tags` AS `t`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`HasSoulPatch`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`HasSoulPatch`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearNickName` = `t0`.`Nickname`) AND (`t`.`GearSquadId` = `t0`.`SquadId`)
+WHERE NOT (CASE
+ WHEN `t0`.`HasSoulPatch` = FALSE THEN FALSE
+ ELSE `t0`.`HasSoulPatch`
+END)
+""");
+ }
+
+ public override async Task Optional_navigation_type_compensation_works_with_conditional_expression(bool async)
+ {
+ await base.Optional_navigation_type_compensation_works_with_conditional_expression(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`GearNickName`, `t`.`GearSquadId`, `t`.`IssueDate`, `t`.`Note`
+FROM `Tags` AS `t`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`HasSoulPatch`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`HasSoulPatch`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearNickName` = `t0`.`Nickname`) AND (`t`.`GearSquadId` = `t0`.`SquadId`)
+WHERE CASE
+ WHEN `t0`.`HasSoulPatch` = TRUE THEN TRUE
+ ELSE FALSE
+END
+""");
+ }
+
+ public override async Task Optional_navigation_type_compensation_works_with_binary_expression(bool async)
+ {
+ await base.Optional_navigation_type_compensation_works_with_binary_expression(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`GearNickName`, `t`.`GearSquadId`, `t`.`IssueDate`, `t`.`Note`
+FROM `Tags` AS `t`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`HasSoulPatch`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`HasSoulPatch`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearNickName` = `t0`.`Nickname`) AND (`t`.`GearSquadId` = `t0`.`SquadId`)
+WHERE (`t0`.`HasSoulPatch` = TRUE) OR (`t`.`Note` LIKE '%Cole%')
+""");
+ }
+
+ public override async Task Optional_navigation_type_compensation_works_with_binary_and_expression(bool async)
+ {
+ await base.Optional_navigation_type_compensation_works_with_binary_and_expression(async);
+
+ AssertSql(
+"""
+SELECT (`t0`.`HasSoulPatch` = TRUE) AND (`t`.`Note` LIKE '%Cole%')
+FROM `Tags` AS `t`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`HasSoulPatch`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`HasSoulPatch`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearNickName` = `t0`.`Nickname`) AND (`t`.`GearSquadId` = `t0`.`SquadId`)
+""");
+ }
+
+ public override async Task Optional_navigation_type_compensation_works_with_projection(bool async)
+ {
+ await base.Optional_navigation_type_compensation_works_with_projection(async);
+
+ AssertSql(
+"""
+SELECT `t0`.`SquadId`
+FROM `Tags` AS `t`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearNickName` = `t0`.`Nickname`) AND (`t`.`GearSquadId` = `t0`.`SquadId`)
+WHERE (`t`.`Note` <> 'K.I.A.') OR `t`.`Note` IS NULL
+""");
+ }
+
+ public override async Task Optional_navigation_type_compensation_works_with_projection_into_anonymous_type(bool async)
+ {
+ await base.Optional_navigation_type_compensation_works_with_projection_into_anonymous_type(async);
+
+ AssertSql(
+"""
+SELECT `t0`.`SquadId`
+FROM `Tags` AS `t`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearNickName` = `t0`.`Nickname`) AND (`t`.`GearSquadId` = `t0`.`SquadId`)
+WHERE (`t`.`Note` <> 'K.I.A.') OR `t`.`Note` IS NULL
+""");
+ }
+
+ public override async Task Optional_navigation_type_compensation_works_with_DTOs(bool async)
+ {
+ await base.Optional_navigation_type_compensation_works_with_DTOs(async);
+
+ AssertSql(
+"""
+SELECT `t0`.`SquadId` AS `Id`
+FROM `Tags` AS `t`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearNickName` = `t0`.`Nickname`) AND (`t`.`GearSquadId` = `t0`.`SquadId`)
+WHERE (`t`.`Note` <> 'K.I.A.') OR `t`.`Note` IS NULL
+""");
+ }
+
+ public override async Task Optional_navigation_type_compensation_works_with_list_initializers(bool async)
+ {
+ await base.Optional_navigation_type_compensation_works_with_list_initializers(async);
+
+ AssertSql(
+"""
+SELECT `t0`.`SquadId`, `t0`.`SquadId` + 1
+FROM `Tags` AS `t`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearNickName` = `t0`.`Nickname`) AND (`t`.`GearSquadId` = `t0`.`SquadId`)
+WHERE (`t`.`Note` <> 'K.I.A.') OR `t`.`Note` IS NULL
+ORDER BY `t`.`Note`
+""");
+ }
+
+ public override async Task Optional_navigation_type_compensation_works_with_array_initializers(bool async)
+ {
+ await base.Optional_navigation_type_compensation_works_with_array_initializers(async);
+
+ AssertSql(
+"""
+SELECT `t0`.`SquadId`
+FROM `Tags` AS `t`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearNickName` = `t0`.`Nickname`) AND (`t`.`GearSquadId` = `t0`.`SquadId`)
+WHERE (`t`.`Note` <> 'K.I.A.') OR `t`.`Note` IS NULL
+""");
+ }
+
+ public override async Task Optional_navigation_type_compensation_works_with_orderby(bool async)
+ {
+ await base.Optional_navigation_type_compensation_works_with_orderby(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`GearNickName`, `t`.`GearSquadId`, `t`.`IssueDate`, `t`.`Note`
+FROM `Tags` AS `t`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearNickName` = `t0`.`Nickname`) AND (`t`.`GearSquadId` = `t0`.`SquadId`)
+WHERE (`t`.`Note` <> 'K.I.A.') OR `t`.`Note` IS NULL
+ORDER BY `t0`.`SquadId`
+""");
+ }
+
+ public override async Task Optional_navigation_type_compensation_works_with_all(bool async)
+ {
+ await base.Optional_navigation_type_compensation_works_with_all(async);
+
+ AssertSql(
+"""
+SELECT NOT EXISTS (
+ SELECT 1
+ FROM `Tags` AS `t`
+ LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+ ) AS `t0` ON (`t`.`GearNickName` = `t0`.`Nickname`) AND (`t`.`GearSquadId` = `t0`.`SquadId`)
+ WHERE ((`t`.`Note` <> 'K.I.A.') OR `t`.`Note` IS NULL) AND (`t0`.`HasSoulPatch` = FALSE))
+""");
+ }
+
+ public override async Task Optional_navigation_type_compensation_works_with_negated_predicate(bool async)
+ {
+ await base.Optional_navigation_type_compensation_works_with_negated_predicate(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`GearNickName`, `t`.`GearSquadId`, `t`.`IssueDate`, `t`.`Note`
+FROM `Tags` AS `t`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`HasSoulPatch`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`HasSoulPatch`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearNickName` = `t0`.`Nickname`) AND (`t`.`GearSquadId` = `t0`.`SquadId`)
+WHERE ((`t`.`Note` <> 'K.I.A.') OR `t`.`Note` IS NULL) AND (`t0`.`HasSoulPatch` = FALSE)
+""");
+ }
+
+ public override async Task Optional_navigation_type_compensation_works_with_contains(bool async)
+ {
+ await base.Optional_navigation_type_compensation_works_with_contains(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t`.`GearNickName`, `t`.`GearSquadId`, `t`.`IssueDate`, `t`.`Note`
+FROM `Tags` AS `t`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`
+ FROM `Officers` AS `o`
+) AS `t0` ON (`t`.`GearNickName` = `t0`.`Nickname`) AND (`t`.`GearSquadId` = `t0`.`SquadId`)
+WHERE ((`t`.`Note` <> 'K.I.A.') OR `t`.`Note` IS NULL) AND EXISTS (
+ SELECT 1
+ FROM (
+ SELECT `g0`.`Nickname`, `g0`.`SquadId`, `g0`.`AssignedCityName`, `g0`.`CityOfBirthName`, `g0`.`FullName`, `g0`.`HasSoulPatch`, `g0`.`LeaderNickname`, `g0`.`LeaderSquadId`, `g0`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g0`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o0`
+ ) AS `t1`
+ WHERE `t1`.`SquadId` = `t0`.`SquadId`)
+""");
+ }
+
+ public override async Task Optional_navigation_type_compensation_works_with_skip(bool async)
+ {
+ await base.Optional_navigation_type_compensation_works_with_skip(async);
+
+ AssertSql();
+ }
+
+ public override async Task Optional_navigation_type_compensation_works_with_take(bool async)
+ {
+ await base.Optional_navigation_type_compensation_works_with_take(async);
+
+ AssertSql();
+ }
+
+ public override async Task Select_correlated_filtered_collection(bool async)
+ {
+ await base.Select_correlated_filtered_collection(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `c`.`Name`, `t0`.`Id`, `t0`.`AmmunitionType`, `t0`.`IsAutomatic`, `t0`.`Name`, `t0`.`OwnerFullName`, `t0`.`SynergyWithId`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`CityOfBirthName`, `g`.`FullName`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`CityOfBirthName`, `o`.`FullName`
+ FROM `Officers` AS `o`
+) AS `t`
+INNER JOIN `Cities` AS `c` ON `t`.`CityOfBirthName` = `c`.`Name`
+LEFT JOIN (
+ SELECT `w`.`Id`, `w`.`AmmunitionType`, `w`.`IsAutomatic`, `w`.`Name`, `w`.`OwnerFullName`, `w`.`SynergyWithId`
+ FROM `Weapons` AS `w`
+ WHERE (`w`.`Name` <> 'Lancer') OR `w`.`Name` IS NULL
+) AS `t0` ON `t`.`FullName` = `t0`.`OwnerFullName`
+WHERE `c`.`Name` IN ('Ephyra', 'Hanover')
+ORDER BY `t`.`Nickname`, `t`.`SquadId`, `c`.`Name`
+""");
+ }
+
+ public override async Task Select_correlated_filtered_collection_with_composite_key(bool async)
+ {
+ await base.Select_correlated_filtered_collection_with_composite_key(async);
+
+ AssertSql(
+$"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t0`.`Nickname`, `t0`.`SquadId`, `t0`.`AssignedCityName`, `t0`.`CityOfBirthName`, `t0`.`FullName`, `t0`.`HasSoulPatch`, `t0`.`LeaderNickname`, `t0`.`LeaderSquadId`, `t0`.`Rank`, `t0`.`Discriminator`
+FROM (
+ SELECT `o`.`Nickname`, `o`.`SquadId`
+ FROM `Officers` AS `o`
+) AS `t`
+LEFT JOIN (
+ SELECT `t1`.`Nickname`, `t1`.`SquadId`, `t1`.`AssignedCityName`, `t1`.`CityOfBirthName`, `t1`.`FullName`, `t1`.`HasSoulPatch`, `t1`.`LeaderNickname`, `t1`.`LeaderSquadId`, `t1`.`Rank`, `t1`.`Discriminator`
+ FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, {MySqlTestHelpers.MySqlBug96947Workaround(@"'Gear'")} AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o0`.`Nickname`, `o0`.`SquadId`, `o0`.`AssignedCityName`, `o0`.`CityOfBirthName`, `o0`.`FullName`, `o0`.`HasSoulPatch`, `o0`.`LeaderNickname`, `o0`.`LeaderSquadId`, `o0`.`Rank`, {MySqlTestHelpers.MySqlBug96947Workaround(@"'Officer'")} AS `Discriminator`
+ FROM `Officers` AS `o0`
+ ) AS `t1`
+ WHERE `t1`.`Nickname` <> 'Dom'
+) AS `t0` ON (`t`.`Nickname` = `t0`.`LeaderNickname`) AND (`t`.`SquadId` = `t0`.`LeaderSquadId`)
+ORDER BY `t`.`Nickname`, `t`.`SquadId`, `t0`.`Nickname`
+""");
+ }
+
+ public override async Task Select_correlated_filtered_collection_works_with_caching(bool async)
+ {
+ await base.Select_correlated_filtered_collection_works_with_caching(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Id`, `t0`.`Nickname`, `t0`.`SquadId`, `t0`.`AssignedCityName`, `t0`.`CityOfBirthName`, `t0`.`FullName`, `t0`.`HasSoulPatch`, `t0`.`LeaderNickname`, `t0`.`LeaderSquadId`, `t0`.`Rank`, `t0`.`Discriminator`
+FROM `Tags` AS `t`
+LEFT JOIN (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t0` ON `t`.`GearNickName` = `t0`.`Nickname`
+ORDER BY `t`.`Note`, `t`.`Id`, `t0`.`Nickname`
+""");
+ }
+
+ public override async Task Join_predicate_value_equals_condition(bool async)
+ {
+ await base.Join_predicate_value_equals_condition(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+INNER JOIN `Weapons` AS `w` ON `w`.`SynergyWithId` IS NOT NULL
+""");
+ }
+
+ public override async Task Join_predicate_value(bool async)
+ {
+ await base.Join_predicate_value(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+INNER JOIN `Weapons` AS `w` ON `t`.`HasSoulPatch` = TRUE
+""");
+ }
+
+ public override async Task Join_predicate_condition_equals_condition(bool async)
+ {
+ await base.Join_predicate_condition_equals_condition(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+INNER JOIN `Weapons` AS `w` ON `w`.`SynergyWithId` IS NOT NULL
+""");
+ }
+
+ public override async Task Left_join_predicate_value_equals_condition(bool async)
+ {
+ await base.Left_join_predicate_value_equals_condition(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+LEFT JOIN `Weapons` AS `w` ON `w`.`SynergyWithId` IS NOT NULL
+""");
+ }
+
+ public override async Task Left_join_predicate_value(bool async)
+ {
+ await base.Left_join_predicate_value(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+LEFT JOIN `Weapons` AS `w` ON `t`.`HasSoulPatch` = TRUE
+""");
+ }
+
+ public override async Task Left_join_predicate_condition_equals_condition(bool async)
+ {
+ await base.Left_join_predicate_condition_equals_condition(async);
+
+ AssertSql(
+"""
+SELECT `t`.`Nickname`, `t`.`SquadId`, `t`.`AssignedCityName`, `t`.`CityOfBirthName`, `t`.`FullName`, `t`.`HasSoulPatch`, `t`.`LeaderNickname`, `t`.`LeaderSquadId`, `t`.`Rank`, `t`.`Discriminator`
+FROM (
+ SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, 'Gear' AS `Discriminator`
+ FROM `Gears` AS `g`
+ UNION ALL
+ SELECT `o`.`Nickname`, `o`.`SquadId`, `o`.`AssignedCityName`, `o`.`CityOfBirthName`, `o`.`FullName`, `o`.`HasSoulPatch`, `o`.`LeaderNickname`, `o`.`LeaderSquadId`, `o`.`Rank`, 'Officer' AS `Discriminator`
+ FROM `Officers` AS `o`
+) AS `t`
+LEFT JOIN `Weapons` AS `w` ON `w`.`SynergyWithId` IS NOT NULL
+""");
+ }
+
+ public override async Task Where_datetimeoffset_now(bool async)
+ {
+ await base.Where_datetimeoffset_now(async);
+
+ AssertSql(
+"""
+SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline`
+FROM `Missions` AS `m`
+WHERE `m`.`Timeline` <> UTC_TIMESTAMP()
+""");
+ }
+
+ public override async Task Where_datetimeoffset_utcnow(bool async)
+ {
+ await base.Where_datetimeoffset_utcnow(async);
+
+ AssertSql(
+"""
+SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline`
+FROM `Missions` AS `m`
+WHERE `m`.`Timeline` <> UTC_TIMESTAMP()
+""");
+ }
+
+ public override async Task Where_datetimeoffset_date_component(bool async)
+ {
+ await base.Where_datetimeoffset_date_component(async);
+
+ AssertSql(
+"""
+@__Date_0='0001-01-01T00:00:00.0000000' (DbType = DateTime)
+
+SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline`
+FROM `Missions` AS `m`
+WHERE CONVERT(`m`.`Timeline`, date) > @__Date_0
+""");
+ }
+
+ public override async Task Where_datetimeoffset_year_component(bool async)
+ {
+ await base.Where_datetimeoffset_year_component(async);
+
+ AssertSql(
+"""
+SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline`
+FROM `Missions` AS `m`
+WHERE EXTRACT(year FROM `m`.`Timeline`) = 2
+""");
+ }
+
+ public override async Task Where_datetimeoffset_month_component(bool async)
+ {
+ await base.Where_datetimeoffset_month_component(async);
+
+ AssertSql(
+"""
+SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline`
+FROM `Missions` AS `m`
+WHERE EXTRACT(month FROM `m`.`Timeline`) = 1
+""");
+ }
+
+ public override async Task Where_datetimeoffset_dayofyear_component(bool async)
+ {
+ await base.Where_datetimeoffset_dayofyear_component(async);
+
+ AssertSql(
+"""
+SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline`
+FROM `Missions` AS `m`
+WHERE DAYOFYEAR(`m`.`Timeline`) = 2
+""");
+ }
+
+ public override async Task Where_datetimeoffset_day_component(bool async)
+ {
+ await base.Where_datetimeoffset_day_component(async);
+
+ AssertSql(
+"""
+SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline`
+FROM `Missions` AS `m`
+WHERE EXTRACT(day FROM `m`.`Timeline`) = 2
+""");
+ }
+
+ public override async Task Where_datetimeoffset_hour_component(bool async)
+ {
+ await base.Where_datetimeoffset_hour_component(async);
+
+ AssertSql(
+"""
+SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline`
+FROM `Missions` AS `m`
+WHERE EXTRACT(hour FROM `m`.`Timeline`) = 10
+""");
+ }
+
+ public override async Task Where_datetimeoffset_minute_component(bool async)
+ {
+ await base.Where_datetimeoffset_minute_component(async);
+
+ AssertSql(
+"""
+SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline`
+FROM `Missions` AS `m`
+WHERE EXTRACT(minute FROM `m`.`Timeline`) = 0
+""");
+ }
+
+ public override async Task Where_datetimeoffset_second_component(bool async)
+ {
+ await base.Where_datetimeoffset_second_component(async);
+
+ AssertSql(
+"""
+SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline`
+FROM `Missions` AS `m`
+WHERE EXTRACT(second FROM `m`.`Timeline`) = 0
+""");
+ }
+
+ public override async Task Where_datetimeoffset_millisecond_component(bool async)
+ {
+ await base.Where_datetimeoffset_millisecond_component(async);
+
+ AssertSql(
+"""
+SELECT `m`.`Id`, `m`.`CodeName`, `m`.`Date`, `m`.`Duration`, `m`.`Rating`, `m`.`Time`, `m`.`Timeline`
+FROM `Missions` AS `m`
+WHERE (EXTRACT(microsecond FROM `m`.`Timeline`)) DIV (1000) = 0
+""");
+ }
+
+ public override async Task DateTimeOffset_DateAdd_AddMonths(bool async)
+ {
+ await base.DateTimeOffset_DateAdd_AddMonths(async);
+
+ AssertSql(
+"""
+SELECT DATE_ADD(`m`.`Timeline`, INTERVAL CAST(1 AS signed) month)
+FROM `Missions` AS `m`
+""");
+ }
+
+ public override async Task DateTimeOffset_DateAdd_AddDays(bool async)
+ {
+ await base.DateTimeOffset_DateAdd_AddDays(async);
+
+ AssertSql(
+"""
+SELECT DATE_ADD(`m`.`Timeline`, INTERVAL CAST(1.0 AS signed) day)
+FROM `Missions` AS `m`
+""");
+ }
+
+ public override async Task DateTimeOffset_DateAdd_AddHours(bool async)
+ {
+ await base.DateTimeOffset_DateAdd_AddHours(async);
+
+ AssertSql(
+"""
+SELECT DATE_ADD(`m`.`Timeline`, INTERVAL CAST(1.0 AS signed) hour)
+FROM `Missions` AS `m`
+""");
+ }
+
+ public override async Task DateTimeOffset_DateAdd_AddMinutes(bool async)
+ {
+ await base.DateTimeOffset_DateAdd_AddMinutes(async);
+
+ AssertSql(
+"""
+SELECT DATE_ADD(`m`.`Timeline`, INTERVAL CAST(1.0 AS signed) minute)
+FROM `Missions` AS `m`
+""");
+ }
+
+ public override async Task DateTimeOffset_DateAdd_AddSeconds(bool async)
+ {
+ await base.DateTimeOffset_DateAdd_AddSeconds(async);
+
+ AssertSql(
+"""
+SELECT DATE_ADD(`m`.`Timeline`, INTERVAL CAST(1.0 AS signed) second)
+FROM `Missions` AS `m`
+""");
+ }
+
+ public override async Task DateTimeOffset_DateAdd_AddMilliseconds(bool async)
+ {
+ await base.DateTimeOffset_DateAdd_AddMilliseconds(async);
+
+ AssertSql(
+"""
+SELECT DATE_ADD(`m`.`Timeline`, INTERVAL 1000 * CAST(300.0 AS signed) microsecond)
+FROM `Missions` AS `m`
+""");
+ }
+
+ public override async Task Where_datetimeoffset_milliseconds_parameter_and_constant(bool async)
+ {
+ var dateTimeOffset = MySqlTestHelpers.GetExpectedValue(new DateTimeOffset(599898024001234567, new TimeSpan(1, 30, 0)));
+
+ // Literal where clause
+ var p = Expression.Parameter(typeof(Mission), "i");
+ var dynamicWhere = Expression.Lambda>(
+ Expression.Equal(
+ Expression.Property(p, "Timeline"),
+ Expression.Constant(dateTimeOffset)
+ ), p);
+
+ await AssertCount(
+ async,
+ ss => ss.Set().Where(dynamicWhere),
+ ss => ss.Set