From a70fde48a650e1734ecdaaa3f871359a8ed66693 Mon Sep 17 00:00:00 2001 From: Philipp Zech Date: Fri, 6 Mar 2020 06:46:40 -0800 Subject: [PATCH 1/3] add arithmetic operators for Sqlite - ef_negate - ef_add - ef_divide - ef_multiply - ef_subtract by ef_add and negated subtrahend --- .../SqliteSqlTranslatingExpressionVisitor.cs | 91 +++++++++++++++---- .../Internal/SqliteRelationalConnection.cs | 21 +++++ 2 files changed, 94 insertions(+), 18 deletions(-) diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs index cca634f5a71..ba683dddbc7 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs @@ -29,15 +29,9 @@ private static readonly IReadOnlyDictionary - { - typeof(decimal), - typeof(TimeSpan), - typeof(ulong) - }, + [ExpressionType.Divide] = new HashSet { typeof(TimeSpan), typeof(ulong) }, [ExpressionType.GreaterThan] = new HashSet { typeof(DateTimeOffset), @@ -63,17 +57,11 @@ private static readonly IReadOnlyDictionary { typeof(ulong) }, - [ExpressionType.Multiply] = new HashSet - { - typeof(decimal), - typeof(TimeSpan), - typeof(ulong) - }, + [ExpressionType.Multiply] = new HashSet { typeof(TimeSpan), typeof(ulong) }, [ExpressionType.Subtract] = new HashSet { typeof(DateTime), typeof(DateTimeOffset), - typeof(decimal), typeof(TimeSpan) } }; @@ -132,8 +120,17 @@ protected override Expression VisitUnary(UnaryExpression unaryExpression) && sqlUnary.OperatorType == ExpressionType.Negate) { var operandType = GetProviderType(sqlUnary.Operand); - if (operandType == typeof(decimal) - || operandType == typeof(TimeSpan)) + if (operandType == typeof(decimal)) + { + return Dependencies.SqlExpressionFactory.Function( + name: "ef_negate", + new[] { sqlUnary.Operand }, + nullable: true, + new[] { true }, + visitedExpression.Type); + } + + if (operandType == typeof(TimeSpan)) { return null; } @@ -177,6 +174,11 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) return DoDecimalCompare(visitedExpression, sqlBinary.OperatorType, sqlBinary.Left, sqlBinary.Right); } + if (AttemptDecimalArithmetic(sqlBinary)) + { + return DoDecimalArithmetics(visitedExpression, sqlBinary.OperatorType, sqlBinary.Left, sqlBinary.Right); + } + if (_restrictedBinaryExpressions.TryGetValue(sqlBinary.OperatorType, out var restrictedTypes) && (restrictedTypes.Contains(GetProviderType(sqlBinary.Left)) || restrictedTypes.Contains(GetProviderType(sqlBinary.Right)))) @@ -291,9 +293,11 @@ private static Type GetProviderType(SqlExpression expression) ?? expression.TypeMapping?.ClrType ?? expression.Type).UnwrapNullableType(); + private static bool AreOperandsDecimals(SqlBinaryExpression sqlExpression) => GetProviderType(sqlExpression.Left) == typeof(decimal) + && GetProviderType(sqlExpression.Right) == typeof(decimal); + private static bool AttemptDecimalCompare(SqlBinaryExpression sqlBinary) => - GetProviderType(sqlBinary.Left) == typeof(decimal) - && GetProviderType(sqlBinary.Right) == typeof(decimal) + AreOperandsDecimals(sqlBinary) && new[] { ExpressionType.GreaterThan, ExpressionType.GreaterThanOrEqual, ExpressionType.LessThan, ExpressionType.LessThanOrEqual @@ -318,5 +322,56 @@ private Expression DoDecimalCompare(SqlExpression visitedExpression, ExpressionT _ => visitedExpression }; } + + private static bool AttemptDecimalArithmetic(SqlBinaryExpression sqlBinary) => + AreOperandsDecimals(sqlBinary) + && new[] { ExpressionType.Add, ExpressionType.Subtract, ExpressionType.Multiply, ExpressionType.Divide }.Contains( + sqlBinary.OperatorType); + + private Expression DoDecimalArithmetics(SqlExpression visitedExpression, ExpressionType op, SqlExpression left, SqlExpression right) + { + return op switch + { + ExpressionType.Add => DecimalArithmeticExpressionFactoryMethod(ResolveFunctionNameFromExpressionType(op), left, right), + ExpressionType.Divide => DecimalArithmeticExpressionFactoryMethod(ResolveFunctionNameFromExpressionType(op), left, right), + ExpressionType.Multiply => DecimalArithmeticExpressionFactoryMethod(ResolveFunctionNameFromExpressionType(op), left, right), + ExpressionType.Subtract => DecimalSubtractExpressionFactoryMethod(left, right), + _ => visitedExpression + }; + + static string ResolveFunctionNameFromExpressionType(ExpressionType expressionType) + { + return expressionType switch + { + ExpressionType.Add => "ef_add", + ExpressionType.Divide => "ef_divide", + ExpressionType.Multiply => "ef_multiply", + ExpressionType.Subtract => "ef_add", + _ => throw new InvalidOperationException() + }; + } + + Expression DecimalArithmeticExpressionFactoryMethod(string name, SqlExpression left, SqlExpression right) + { + return Dependencies.SqlExpressionFactory.Function( + name, + new[] { left, right }, + nullable: true, + new[] { true, true }, + visitedExpression.Type); + } + + Expression DecimalSubtractExpressionFactoryMethod(SqlExpression left, SqlExpression right) + { + var subtrahend = Dependencies.SqlExpressionFactory.Function( + "ef_negate", + new[] { right }, + nullable: true, + new[] { true }, + visitedExpression.Type); + + return DecimalArithmeticExpressionFactoryMethod(ResolveFunctionNameFromExpressionType(op), left, subtrahend); + } + } } } diff --git a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteRelationalConnection.cs b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteRelationalConnection.cs index 835747fabbd..02ded1c1886 100644 --- a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteRelationalConnection.cs +++ b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteRelationalConnection.cs @@ -134,14 +134,35 @@ private void InitializeDbConnection(DbConnection connection) % Convert.ToDouble(divisor, CultureInfo.InvariantCulture); }); + sqliteConnection.CreateFunction( + name: "ef_add", + (decimal? left, decimal? right) => left + right, + isDeterministic: true); + + sqliteConnection.CreateFunction( + name: "ef_divide", + (decimal? dividend, decimal? divisor) => dividend / divisor, + isDeterministic: true); + sqliteConnection.CreateFunction( name: "ef_compare", (decimal? left, decimal? right) => left.HasValue && right.HasValue ? decimal.Compare(left.Value, right.Value) : default(int?), isDeterministic: true); + + sqliteConnection.CreateFunction( + name: "ef_multiply", + (decimal? left, decimal? right) => left * right, + isDeterministic: true); + + sqliteConnection.CreateFunction( + name: "ef_negate", + (decimal? m) => -m, + isDeterministic: true); } else + { _logger.UnexpectedConnectionTypeWarning(connection.GetType()); } From 2eacef051c2597ca0806d526d5074707b07b8bd2 Mon Sep 17 00:00:00 2001 From: maumar Date: Wed, 8 Jul 2020 20:27:15 -0700 Subject: [PATCH 2/3] adding and fixing tests --- .../BuiltInDataTypesSqliteTest.cs | 47 +++++++++++++++++++ ...thwindAggregateOperatorsQuerySqliteTest.cs | 10 ++-- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/test/EFCore.Sqlite.FunctionalTests/BuiltInDataTypesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BuiltInDataTypesSqliteTest.cs index 9fbcc0e87d8..1d22abb7385 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BuiltInDataTypesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BuiltInDataTypesSqliteTest.cs @@ -1505,6 +1505,53 @@ public override void Object_to_string_conversion() WHERE ""b"".""Id"" = 13"); } + [ConditionalFact] + public virtual void Projecting_aritmetic_operations_on_decimals() + { + using var context = CreateContext(); + var expected = (from dt1 in context.Set().ToList() + from dt2 in context.Set().ToList() + orderby dt1.Id, dt2.Id + select new + { + add = dt1.TestDecimal + dt2.TestDecimal, + subtract = dt1.TestDecimal - dt2.TestDecimal, + multiply = dt1.TestDecimal * dt2.TestDecimal, + divide = dt1.TestDecimal / dt2.TestDecimal, + negate = -dt1.TestDecimal + }).ToList(); + + Fixture.TestSqlLoggerFactory.Clear(); + + var actual = (from dt1 in context.Set() + from dt2 in context.Set() + orderby dt1.Id, dt2.Id + select new + { + add = dt1.TestDecimal + dt2.TestDecimal, + subtract = dt1.TestDecimal - dt2.TestDecimal, + multiply = dt1.TestDecimal * dt2.TestDecimal, + divide = dt1.TestDecimal / dt2.TestDecimal, + negate = -dt1.TestDecimal + }).ToList(); + + Assert.Equal(expected.Count, actual.Count); + for (var i = 0; i < expected.Count; i++) + { + Assert.Equal(expected[i].add, actual[i].add); + Assert.Equal(expected[i].subtract, actual[i].subtract); + Assert.Equal(expected[i].multiply, actual[i].multiply); + Assert.Equal(expected[i].divide, actual[i].divide); + Assert.Equal(expected[i].negate, actual[i].negate); + } + + AssertSql( + @"SELECT ef_add(""b"".""TestDecimal"", ""b0"".""TestDecimal"") AS ""add"", ef_add(""b"".""TestDecimal"", ef_negate(""b0"".""TestDecimal"")) AS ""subtract"", ef_multiply(""b"".""TestDecimal"", ""b0"".""TestDecimal"") AS ""multiply"", ef_divide(""b"".""TestDecimal"", ""b0"".""TestDecimal"") AS ""divide"", ef_negate(""b"".""TestDecimal"") AS ""negate"" +FROM ""BuiltInDataTypes"" AS ""b"" +CROSS JOIN ""BuiltInDataTypes"" AS ""b0"" +ORDER BY ""b"".""Id"", ""b0"".""Id"""); + } + private void AssertTranslationFailed(Action testCode) => Assert.Contains( CoreStrings.TranslationFailed("").Substring(21), diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqliteTest.cs index c4add4c207f..7596ed52576 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqliteTest.cs @@ -1,8 +1,10 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.TestUtilities; +using Xunit; using Xunit.Abstractions; namespace Microsoft.EntityFrameworkCore.Query @@ -17,15 +19,15 @@ public NorthwindAggregateOperatorsQuerySqliteTest(NorthwindQuerySqliteFixture AssertTranslationFailed(() => base.Sum_with_division_on_decimal(async)); + => Assert.ThrowsAsync(() => base.Sum_with_division_on_decimal(async)); public override Task Sum_with_division_on_decimal_no_significant_digits(bool async) - => AssertTranslationFailed(() => base.Sum_with_division_on_decimal_no_significant_digits(async)); + => Assert.ThrowsAsync(() => base.Sum_with_division_on_decimal_no_significant_digits(async)); public override Task Average_with_division_on_decimal(bool async) - => AssertTranslationFailed(() => base.Average_with_division_on_decimal(async)); + => Assert.ThrowsAsync(() => base.Average_with_division_on_decimal(async)); public override Task Average_with_division_on_decimal_no_significant_digits(bool async) - => AssertTranslationFailed(() => base.Average_with_division_on_decimal_no_significant_digits(async)); + => Assert.ThrowsAsync(() => base.Average_with_division_on_decimal_no_significant_digits(async)); } } From 9e6365603eff119c206df31daa3bfe4c80581776 Mon Sep 17 00:00:00 2001 From: maumar Date: Tue, 23 Jun 2020 23:02:24 -0700 Subject: [PATCH 3/3] Fix to #18871 - Support for indirect joins which are using non-equal conditions When validating join key expression we now allow non-equality binary ops. Fixes #18871 --- .../Query/SqlExpressions/SelectExpression.cs | 49 ++++-- .../Query/SqlNullabilityProcessor.cs | 7 +- .../NorthwindMiscellaneousQueryCosmosTest.cs | 6 + .../Query/NorthwindSelectQueryCosmosTest.cs | 18 ++ .../Query/GearsOfWarQueryInMemoryTest.cs | 4 + .../Query/GearsOfWarQueryTestBase.cs | 154 ++++++++++++++++++ .../Query/ManyToManyQueryTestBase.cs | 13 +- .../Query/NorthwindIncludeQueryTestBase.cs | 15 ++ .../NorthwindMiscellaneousQueryTestBase.cs | 22 +++ .../Query/NorthwindSelectQueryTestBase.cs | 56 +++++++ .../ComplexNavigationsQuerySqlServerTest.cs | 6 +- .../Query/GearsOfWarQuerySqlServerTest.cs | 133 +++++++++++++++ .../Query/ManyToManyQuerySqlServerTest.cs | 14 ++ .../NorthwindIncludeQuerySqlServerTest.cs | 18 ++ ...orthwindMiscellaneousQuerySqlServerTest.cs | 27 +++ .../NorthwindSelectQuerySqlServerTest.cs | 44 +++++ ...NorthwindSplitIncludeQuerySqlServerTest.cs | 29 ++++ .../ComplexNavigationsQuerySqliteTest.cs | 1 - .../ComplexNavigationsWeakQuerySqliteTest.cs | 1 - .../Query/GearsOfWarQuerySqliteTest.cs | 1 + .../Query/ManyToManyQuerySqliteTest.cs | 1 - ...rthwindIncludeNoTrackingQuerySqliteTest.cs | 2 + .../Query/NorthwindIncludeQuerySqliteTest.cs | 1 + .../NorthwindMiscellaneousQuerySqliteTest.cs | 4 + .../Query/NorthwindSelectQuerySqliteTest.cs | 6 + ...ndSplitIncludeNoTrackingQuerySqliteTest.cs | 2 + .../NorthwindSplitIncludeQuerySqliteTest.cs | 1 + .../NorthwindStringIncludeQuerySqliteTest.cs | 3 + .../Query/TPTGearsOfWarQuerySqliteTest.cs | 2 + 29 files changed, 620 insertions(+), 20 deletions(-) diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs index 1113f84b800..26698b2c28c 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs @@ -28,6 +28,16 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions // Class is sealed because there are no public/protected constructors. Can be unsealed if this is changed. public sealed class SelectExpression : TableExpressionBase { + private static readonly Dictionary _mirroredOperationMap = new Dictionary + { + { ExpressionType.Equal, ExpressionType.Equal }, + { ExpressionType.NotEqual, ExpressionType.NotEqual }, + { ExpressionType.LessThan, ExpressionType.GreaterThan }, + { ExpressionType.LessThanOrEqual, ExpressionType.GreaterThanOrEqual }, + { ExpressionType.GreaterThan, ExpressionType.LessThan }, + { ExpressionType.GreaterThanOrEqual, ExpressionType.LessThanOrEqual }, + }; + private readonly IDictionary> _entityProjectionCache = new Dictionary>(); @@ -1566,14 +1576,20 @@ private static SqlExpression MakeNullable(SqlExpression sqlExpression) return (NewArrayInit(typeof(object), updatedExpressions), comparers); } - private SqlExpression TryExtractJoinKey(SelectExpression selectExpression) + private SqlExpression TryExtractJoinKey(SelectExpression selectExpression, bool allowNonEquality) { if (selectExpression.Limit == null && selectExpression.Offset == null && selectExpression.Predicate != null) { var columnExpressions = new List(); - var joinPredicate = TryExtractJoinKey(selectExpression, selectExpression.Predicate, columnExpressions, out var predicate); + var joinPredicate = TryExtractJoinKey( + selectExpression, + selectExpression.Predicate, + columnExpressions, + allowNonEquality, + out var predicate); + if (joinPredicate != null) { joinPredicate = RemoveRedundantNullChecks(joinPredicate, columnExpressions); @@ -1591,11 +1607,12 @@ private SqlExpression TryExtractJoinKey( SelectExpression selectExpression, SqlExpression predicate, List columnExpressions, + bool allowNonEquality, out SqlExpression updatedPredicate) { if (predicate is SqlBinaryExpression sqlBinaryExpression) { - var joinPredicate = ValidateKeyComparison(selectExpression, sqlBinaryExpression, columnExpressions); + var joinPredicate = ValidateKeyComparison(selectExpression, sqlBinaryExpression, columnExpressions, allowNonEquality); if (joinPredicate != null) { updatedPredicate = null; @@ -1606,9 +1623,9 @@ private SqlExpression TryExtractJoinKey( if (sqlBinaryExpression.OperatorType == ExpressionType.AndAlso) { var leftJoinKey = TryExtractJoinKey( - selectExpression, sqlBinaryExpression.Left, columnExpressions, out var leftPredicate); + selectExpression, sqlBinaryExpression.Left, columnExpressions, allowNonEquality, out var leftPredicate); var rightJoinKey = TryExtractJoinKey( - selectExpression, sqlBinaryExpression.Right, columnExpressions, out var rightPredicate); + selectExpression, sqlBinaryExpression.Right, columnExpressions, allowNonEquality, out var rightPredicate); updatedPredicate = CombineNonNullExpressions(leftPredicate, rightPredicate); @@ -1629,9 +1646,18 @@ private static SqlExpression CombineNonNullExpressions(SqlExpression left, SqlEx : right; private SqlBinaryExpression ValidateKeyComparison( - SelectExpression inner, SqlBinaryExpression sqlBinaryExpression, List columnExpressions) + SelectExpression inner, + SqlBinaryExpression sqlBinaryExpression, + List columnExpressions, + bool allowNonEquality) { - if (sqlBinaryExpression.OperatorType == ExpressionType.Equal) + if (sqlBinaryExpression.OperatorType == ExpressionType.Equal + || (allowNonEquality && + (sqlBinaryExpression.OperatorType == ExpressionType.NotEqual + || sqlBinaryExpression.OperatorType == ExpressionType.GreaterThan + || sqlBinaryExpression.OperatorType == ExpressionType.GreaterThanOrEqual + || sqlBinaryExpression.OperatorType == ExpressionType.LessThan + || sqlBinaryExpression.OperatorType == ExpressionType.LessThanOrEqual))) { if (sqlBinaryExpression.Left is ColumnExpression leftColumn && sqlBinaryExpression.Right is ColumnExpression rightColumn) @@ -1649,9 +1675,12 @@ private SqlBinaryExpression ValidateKeyComparison( { columnExpressions.Add(rightColumn); - return sqlBinaryExpression.Update( + return new SqlBinaryExpression( + _mirroredOperationMap[sqlBinaryExpression.OperatorType], sqlBinaryExpression.Right, - sqlBinaryExpression.Left); + sqlBinaryExpression.Left, + sqlBinaryExpression.Type, + sqlBinaryExpression.TypeMapping); } } } @@ -2024,7 +2053,7 @@ private void AddJoin( innerSelectExpression.Limit = null; innerSelectExpression.Offset = null; - joinPredicate = TryExtractJoinKey(innerSelectExpression); + joinPredicate = TryExtractJoinKey(innerSelectExpression, allowNonEquality: limit == null && offset == null); if (joinPredicate != null) { var containsOuterReference = new SelectExpressionCorrelationFindingExpressionVisitor(this) diff --git a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs index dd541ee3892..2990e623f1b 100644 --- a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs +++ b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs @@ -1041,7 +1041,12 @@ private SqlExpression ProcessJoinPredicate(SqlExpression predicate) return result; } - if (sqlBinaryExpression.OperatorType == ExpressionType.AndAlso) + if (sqlBinaryExpression.OperatorType == ExpressionType.AndAlso + || sqlBinaryExpression.OperatorType == ExpressionType.NotEqual + || sqlBinaryExpression.OperatorType == ExpressionType.GreaterThan + || sqlBinaryExpression.OperatorType == ExpressionType.GreaterThanOrEqual + || sqlBinaryExpression.OperatorType == ExpressionType.LessThan + || sqlBinaryExpression.OperatorType == ExpressionType.LessThanOrEqual) { return Visit(sqlBinaryExpression, allowOptimizedExpansion: true, out _); } diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs index 9da2a368969..57fdd89e9a0 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs @@ -2651,6 +2651,12 @@ FROM root c WHERE ((c[""Discriminator""] = ""Customer"") AND (c[""City""] = ""Seattle""))"); } + [ConditionalTheory(Skip = "Issue#17246")] + public override Task DefaultIfEmpty_in_subquery_nested_filter_order_comparison(bool async) + { + return base.DefaultIfEmpty_in_subquery_nested_filter_order_comparison(async); + } + [ConditionalTheory(Skip = "Issue #17246")] public override async Task OrderBy_skip_take(bool async) { diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindSelectQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindSelectQueryCosmosTest.cs index 17701dea7c3..f50d7cd0f22 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindSelectQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindSelectQueryCosmosTest.cs @@ -962,6 +962,24 @@ public override Task SelectMany_correlated_with_outer_4(bool async) return base.SelectMany_correlated_with_outer_4(async); } + [ConditionalTheory(Skip = "Issue#17246")] + public override Task SelectMany_correlated_with_outer_5(bool async) + { + return base.SelectMany_correlated_with_outer_5(async); + } + + [ConditionalTheory(Skip = "Issue#17246")] + public override Task SelectMany_correlated_with_outer_6(bool async) + { + return base.SelectMany_correlated_with_outer_6(async); + } + + [ConditionalTheory(Skip = "Issue#17246")] + public override Task SelectMany_correlated_with_outer_7(bool async) + { + return base.SelectMany_correlated_with_outer_7(async); + } + [ConditionalTheory(Skip = "Issue#17246")] public override Task FirstOrDefault_over_empty_collection_of_value_type_returns_correct_results(bool async) { diff --git a/test/EFCore.InMemory.FunctionalTests/Query/GearsOfWarQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/GearsOfWarQueryInMemoryTest.cs index 19e1110272c..30358998b28 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/GearsOfWarQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/GearsOfWarQueryInMemoryTest.cs @@ -70,5 +70,9 @@ public override Task Client_member_and_unsupported_string_Equals_in_the_same_que [ConditionalTheory(Skip = "issue #17386")] public override Task Client_eval_followed_by_set_operation_throws_meaningful_exception(bool async) => base.Client_eval_followed_by_set_operation_throws_meaningful_exception(async); + + [ConditionalTheory(Skip = "issue #17537")] + public override Task SelectMany_predicate_with_non_equality_comparison_with_Take_doesnt_convert_to_join(bool async) + => base.SelectMany_predicate_with_non_equality_comparison_with_Take_doesnt_convert_to_join(async); } } diff --git a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs index eff2b8ac670..d80bb52c69b 100644 --- a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs @@ -6353,6 +6353,42 @@ from w in g.Weapons.Where(ww => ww.IsAutomatic == isAutomatic).DefaultIfEmpty() }); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task SelectMany_Where_DefaultIfEmpty_with_navigation_in_the_collection_selector_not_equal(bool async) + { + var isAutomatic = true; + + return AssertQuery( + async, + ss => from g in ss.Set() + from w in g.Weapons.Where(ww => ww.IsAutomatic != isAutomatic).DefaultIfEmpty() + select new + { + g.Nickname, + g.FullName, + Collection = w != null + }); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task SelectMany_Where_DefaultIfEmpty_with_navigation_in_the_collection_selector_order_comparison(bool async) + { + var prm = 1; + + return AssertQuery( + async, + ss => from g in ss.Set() + from w in g.Weapons.Where(ww => ww.Id > prm).DefaultIfEmpty() + select new + { + g.Nickname, + g.FullName, + Collection = w != null + }); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Join_with_inner_being_a_subquery_projecting_single_property(bool async) @@ -7614,6 +7650,124 @@ public virtual Task Groupby_anonymous_type_with_navigations_followed_up_by_anony assertOrder: true); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task SelectMany_predicate_with_non_equality_comparison_converted_to_inner_join(bool async) + { + return AssertQuery( + async, + ss => from g in ss.Set() + from w in ss.Set().Where(x => x.OwnerFullName != g.FullName) + orderby g.Nickname, w.Id + select new { g, w }, + assertOrder: true, + elementAsserter: (e, a) => + { + AssertEqual(e.g, a.g); + AssertEqual(e.w, a.w); + }); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task SelectMany_predicate_with_non_equality_comparison_DefaultIfEmpty_converted_to_left_join(bool async) + { + return AssertQuery( + async, + ss => from g in ss.Set() + from w in ss.Set().Where(x => x.OwnerFullName != g.FullName).DefaultIfEmpty() + orderby g.Nickname, w.Id + select new { g, w }, + assertOrder: true, + elementAsserter: (e, a) => + { + AssertEqual(e.g, a.g); + AssertEqual(e.w, a.w); + }); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task SelectMany_predicate_after_navigation_with_non_equality_comparison_DefaultIfEmpty_converted_to_left_join(bool async) + { + return AssertQuery( + async, + ss => from g in ss.Set() + from w in ss.Set().Select(x => x.SynergyWith).Where(x => x.OwnerFullName != g.FullName).DefaultIfEmpty() + orderby g.Nickname, w.Id + select new { g, w }, + ss => from g in ss.Set() + from w in ss.Set().Select(x => x.SynergyWith).Where(x => x.OwnerFullName != g.FullName).MaybeDefaultIfEmpty() + orderby g.Nickname, w.MaybeScalar(xx => xx.Id) + select new { g, w }, + assertOrder: true, + elementAsserter: (e, a) => + { + AssertEqual(e.g, a.g); + AssertEqual(e.w, a.w); + }); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task SelectMany_without_result_selector_and_non_equality_comparison_converted_to_join(bool async) + { + return AssertQuery( + async, + ss => ss.Set().SelectMany(g => ss.Set().Where(x => x.OwnerFullName != g.FullName).DefaultIfEmpty())); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_collection_projection_with_order_comparison_predicate_converted_to_join(bool async) + { + return AssertQuery( + async, + ss => ss.Set().OrderBy(g => g.Nickname).Select(g => g.Weapons.Where(x => x.Id > g.SquadId).ToList()), + assertOrder: true, + elementAsserter: (e, a) => AssertCollection(e, a, elementSorter: ee => ee.Id)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_collection_projection_with_order_comparison_predicate_converted_to_join2(bool async) + { + return AssertQuery( + async, + ss => ss.Set().OrderBy(g => g.Nickname).Select(g => g.Weapons.Where(x => x.Id >= g.SquadId).ToList()), + assertOrder: true, + elementAsserter: (e, a) => AssertCollection(e, a, elementSorter: ee => ee.Id)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_collection_projection_with_order_comparison_predicate_converted_to_join3(bool async) + { + return AssertQuery( + async, + ss => ss.Set().OrderBy(g => g.Nickname).Select(g => g.Weapons.Where(x => x.Id <= g.SquadId).ToList()), + assertOrder: true, + elementAsserter: (e, a) => AssertCollection(e, a, elementSorter: ee => ee.Id)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task SelectMany_predicate_with_non_equality_comparison_with_Take_doesnt_convert_to_join(bool async) + { + return AssertQuery( + async, + ss => from g in ss.Set() + from w in ss.Set().Where(x => x.OwnerFullName != g.FullName).Take(3) + orderby g.Nickname, w.Id + select new { g, w }, + assertOrder: true, + elementAsserter: (e, a) => + { + AssertEqual(e.g, a.g); + AssertEqual(e.w, a.w); + }); + } + protected GearsOfWarContext CreateContext() => Fixture.CreateContext(); protected virtual void ClearLog() diff --git a/test/EFCore.Specification.Tests/Query/ManyToManyQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/ManyToManyQueryTestBase.cs index ae716c76e74..09b46e1e679 100644 --- a/test/EFCore.Specification.Tests/Query/ManyToManyQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/ManyToManyQueryTestBase.cs @@ -787,7 +787,18 @@ public virtual async Task Throws_when_different_filtered_then_include_via_differ .Replace("\r", "").Replace("\n", "")); } - // When adding include test here always add a split version in relational layer. + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_many_over_skip_navigation_where_non_equality(bool async) + { + return AssertQuery( + async, + ss => from r in ss.Set() + from t in r.TwoSkip.Where(x => x.Id != r.Id).DefaultIfEmpty() + select t); + } + + // When adding include test here always add a tracking version and a split version in relational layer. // Keep this line at the bottom for next dev writing tests to see. protected ManyToManyContext CreateContext() => Fixture.CreateContext(); diff --git a/test/EFCore.Specification.Tests/Query/NorthwindIncludeQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindIncludeQueryTestBase.cs index 8964be53167..128cb78bdec 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindIncludeQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindIncludeQueryTestBase.cs @@ -466,6 +466,21 @@ where c.CustomerID.StartsWith("F") entryCount: 71); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Include_collection_with_outer_apply_with_filter_non_equality(bool async) + { + return AssertQuery( + async, + ss => from c in ss.Set().Include(c => c.Orders) + from o in ss.Set().Where(o => o.CustomerID != c.CustomerID) + .OrderBy(o => c.CustomerID).Take(5).DefaultIfEmpty() + where c.CustomerID.StartsWith("F") + select c, + elementAsserter: (e, a) => AssertInclude(e, a, new ExpectedInclude(c => c.Orders)), + entryCount: 71); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Include_collection_on_join_clause_with_order_by_and_filter(bool async) diff --git a/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs index e57ec90b63e..822f19f9710 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs @@ -4148,6 +4148,28 @@ from o2 in ss.Set().Where(o => o.CustomerID == c.CustomerID).DefaultIfEmp e => (e.CustomerID, e.OrderID)); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task DefaultIfEmpty_in_subquery_nested_filter_order_comparison(bool async) + { + return AssertQuery( + async, + ss => + (from c in ss.Set().Where(c => c.City == "Seattle") + from o1 in ss.Set().Where(o => o.OrderID > 15000).DefaultIfEmpty() + from o2 in ss.Set().Where(o => o.OrderID <= c.CustomerID.Length).DefaultIfEmpty() + where o1 != null && o2 != null + orderby o1.OrderID, o2.OrderDate + select new + { + c.CustomerID, + o1.OrderID, + o2.OrderDate + }), + e => (e.CustomerID, e.OrderID)); + } + + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task OrderBy_skip_take(bool async) diff --git a/test/EFCore.Specification.Tests/Query/NorthwindSelectQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindSelectQueryTestBase.cs index a49f2502f19..41f7ee23f2b 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindSelectQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindSelectQueryTestBase.cs @@ -1375,6 +1375,62 @@ from o in ss.Set().Where(o => c.CustomerID == o.CustomerID) entryCount: 268); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task SelectMany_correlated_with_outer_5(bool async) + { + return AssertQuery( + async, + ss => from c in ss.Set() + from o in ss.Set().Where(o => c.CustomerID != o.CustomerID).Select(o => c.City).DefaultIfEmpty() + select new { c, o }, + elementSorter: e => (e.c.CustomerID, e.o), + elementAsserter: (e, a) => + { + AssertEqual(e.c, a.c); + Assert.Equal(e.o, a.o); + }, + entryCount: 91); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task SelectMany_correlated_with_outer_6(bool async) + { + return AssertQuery( + async, + ss => from c in ss.Set() + from o in ss.Set().Where(o => c.CustomerID != o.CustomerID) + .OrderBy(o => c.City).ThenBy(o => o.OrderID).Take(2).DefaultIfEmpty() + select new { c, o }, + elementSorter: e => (e.c.CustomerID, e.o?.OrderID), + elementAsserter: (e, a) => + { + AssertEqual(e.c, a.c); + AssertEqual(e.o, a.o); + }, + entryCount: 94); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task SelectMany_correlated_with_outer_7(bool async) + { + return AssertQuery( + async, + ss => from c in ss.Set() + from o in ss.Set().Where(o => c.CustomerID.Length >= o.CustomerID.Length) + .OrderBy(o => c.City).ThenBy(o => o.OrderID).Take(2).DefaultIfEmpty() + select new { c, o }, + elementSorter: e => (e.c.CustomerID, e.o?.OrderID), + elementAsserter: (e, a) => + { + AssertEqual(e.c, a.c); + AssertEqual(e.o, a.o); + }, + entryCount: 93); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task FirstOrDefault_over_empty_collection_of_value_type_returns_correct_results(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs index e01b42e2302..cb3361de315 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs @@ -4407,11 +4407,7 @@ FROM [LevelOne] AS [l] INNER JOIN [LevelTwo] AS [l0] ON [l].[Id] = [l0].[Level1_Required_Id] INNER JOIN [LevelThree] AS [l1] ON [l0].[Id] = [l1].[Level2_Required_Id] INNER JOIN [LevelFour] AS [l2] ON [l1].[Id] = [l2].[Level3_Required_Id] -OUTER APPLY ( - SELECT [l3].[Id], [l3].[Date], [l3].[Name], [l3].[OneToMany_Optional_Self_Inverse1Id], [l3].[OneToMany_Required_Self_Inverse1Id], [l3].[OneToOne_Optional_Self1Id] - FROM [LevelOne] AS [l3] - WHERE ([l3].[Id] <= [l0].[Id]) AND (([l2].[Name] = [l3].[Name]) OR ([l2].[Name] IS NULL AND [l3].[Name] IS NULL)) -) AS [t]"); +LEFT JOIN [LevelOne] AS [l3] ON ([l0].[Id] >= [l3].[Id]) AND (([l2].[Name] = [l3].[Name]) OR ([l2].[Name] IS NULL AND [l3].[Name] IS NULL))"); } public override async Task Nested_SelectMany_correlated_with_join_table_correctly_translated_to_apply(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index 5056f7ba756..e2217dd8b97 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -5971,6 +5971,44 @@ FROM [Weapons] AS [w] ) AS [t] ON [g].[FullName] = [t].[OwnerFullName]"); } + public override async Task SelectMany_Where_DefaultIfEmpty_with_navigation_in_the_collection_selector_not_equal(bool async) + { + await base.SelectMany_Where_DefaultIfEmpty_with_navigation_in_the_collection_selector_not_equal(async); + + AssertSql( + @"@__isAutomatic_0='True' + +SELECT [g].[Nickname], [g].[FullName], CASE + WHEN [t].[Id] IS NOT NULL THEN CAST(1 AS bit) + ELSE CAST(0 AS bit) +END AS [Collection] +FROM [Gears] AS [g] +LEFT JOIN ( + SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] + FROM [Weapons] AS [w] + WHERE [w].[IsAutomatic] <> @__isAutomatic_0 +) AS [t] ON [g].[FullName] = [t].[OwnerFullName]"); + } + + public override async Task SelectMany_Where_DefaultIfEmpty_with_navigation_in_the_collection_selector_order_comparison(bool async) + { + await base.SelectMany_Where_DefaultIfEmpty_with_navigation_in_the_collection_selector_order_comparison(async); + + AssertSql( + @"@__prm_0='1' + +SELECT [g].[Nickname], [g].[FullName], CASE + WHEN [t].[Id] IS NOT NULL THEN CAST(1 AS bit) + ELSE CAST(0 AS bit) +END AS [Collection] +FROM [Gears] AS [g] +LEFT JOIN ( + SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] + FROM [Weapons] AS [w] + WHERE [w].[Id] > @__prm_0 +) AS [t] ON [g].[FullName] = [t].[OwnerFullName]"); + } + public override async Task Join_with_inner_being_a_subquery_projecting_single_property(bool async) { await base.Join_with_inner_being_a_subquery_projecting_single_property(async); @@ -7048,6 +7086,101 @@ FROM [Weapons] AS [w] ORDER BY [c].[Location]"); } + public override async Task SelectMany_predicate_with_non_equality_comparison_converted_to_inner_join(bool async) + { + await base.SelectMany_predicate_with_non_equality_comparison_converted_to_inner_join(async); + + AssertSql( + @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank], [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] +FROM [Gears] AS [g] +INNER JOIN [Weapons] AS [w] ON ([g].[FullName] <> [w].[OwnerFullName]) OR [w].[OwnerFullName] IS NULL +ORDER BY [g].[Nickname], [w].[Id]"); + } + + public override async Task SelectMany_predicate_with_non_equality_comparison_DefaultIfEmpty_converted_to_left_join(bool async) + { + await base.SelectMany_predicate_with_non_equality_comparison_DefaultIfEmpty_converted_to_left_join(async); + + AssertSql( + @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank], [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] +FROM [Gears] AS [g] +LEFT JOIN [Weapons] AS [w] ON ([g].[FullName] <> [w].[OwnerFullName]) OR [w].[OwnerFullName] IS NULL +ORDER BY [g].[Nickname], [w].[Id]"); + } + + public override async Task SelectMany_predicate_after_navigation_with_non_equality_comparison_DefaultIfEmpty_converted_to_left_join(bool async) + { + await base.SelectMany_predicate_after_navigation_with_non_equality_comparison_DefaultIfEmpty_converted_to_left_join(async); + + AssertSql( + @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank], [t].[Id], [t].[AmmunitionType], [t].[IsAutomatic], [t].[Name], [t].[OwnerFullName], [t].[SynergyWithId] +FROM [Gears] AS [g] +LEFT JOIN ( + SELECT [w0].[Id], [w0].[AmmunitionType], [w0].[IsAutomatic], [w0].[Name], [w0].[OwnerFullName], [w0].[SynergyWithId], [w].[Id] AS [Id0] + FROM [Weapons] AS [w] + LEFT JOIN [Weapons] AS [w0] ON [w].[SynergyWithId] = [w0].[Id] +) AS [t] ON ([g].[FullName] <> [t].[OwnerFullName]) OR [t].[OwnerFullName] IS NULL +ORDER BY [g].[Nickname], [t].[Id]"); + } + + public override async Task SelectMany_without_result_selector_and_non_equality_comparison_converted_to_join(bool async) + { + await base.SelectMany_without_result_selector_and_non_equality_comparison_converted_to_join(async); + + AssertSql( + @"SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] +FROM [Gears] AS [g] +LEFT JOIN [Weapons] AS [w] ON ([g].[FullName] <> [w].[OwnerFullName]) OR [w].[OwnerFullName] IS NULL"); + } + + public override async Task Filtered_collection_projection_with_order_comparison_predicate_converted_to_join(bool async) + { + await base.Filtered_collection_projection_with_order_comparison_predicate_converted_to_join(async); + + AssertSql( + @"SELECT [g].[Nickname], [g].[SquadId], [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] +FROM [Gears] AS [g] +LEFT JOIN [Weapons] AS [w] ON ([g].[FullName] = [w].[OwnerFullName]) AND ([g].[SquadId] < [w].[Id]) +ORDER BY [g].[Nickname], [g].[SquadId], [w].[Id]"); + } + + public override async Task Filtered_collection_projection_with_order_comparison_predicate_converted_to_join2(bool async) + { + await base.Filtered_collection_projection_with_order_comparison_predicate_converted_to_join2(async); + + AssertSql( + @"SELECT [g].[Nickname], [g].[SquadId], [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] +FROM [Gears] AS [g] +LEFT JOIN [Weapons] AS [w] ON ([g].[FullName] = [w].[OwnerFullName]) AND ([g].[SquadId] <= [w].[Id]) +ORDER BY [g].[Nickname], [g].[SquadId], [w].[Id]"); + } + + public override async Task Filtered_collection_projection_with_order_comparison_predicate_converted_to_join3(bool async) + { + await base.Filtered_collection_projection_with_order_comparison_predicate_converted_to_join3(async); + + AssertSql( + @"SELECT [g].[Nickname], [g].[SquadId], [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] +FROM [Gears] AS [g] +LEFT JOIN [Weapons] AS [w] ON ([g].[FullName] = [w].[OwnerFullName]) AND ([g].[SquadId] >= [w].[Id]) +ORDER BY [g].[Nickname], [g].[SquadId], [w].[Id]"); + } + + public override async Task SelectMany_predicate_with_non_equality_comparison_with_Take_doesnt_convert_to_join(bool async) + { + await base.SelectMany_predicate_with_non_equality_comparison_with_Take_doesnt_convert_to_join(async); + + AssertSql( + @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank], [t].[Id], [t].[AmmunitionType], [t].[IsAutomatic], [t].[Name], [t].[OwnerFullName], [t].[SynergyWithId] +FROM [Gears] AS [g] +CROSS APPLY ( + SELECT TOP(3) [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] + FROM [Weapons] AS [w] + WHERE ([w].[OwnerFullName] <> [g].[FullName]) OR [w].[OwnerFullName] IS NULL +) AS [t] +ORDER BY [g].[Nickname], [t].[Id]"); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ManyToManyQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ManyToManyQuerySqlServerTest.cs index 7d35b2f00f2..abb59cd58d6 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ManyToManyQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ManyToManyQuerySqlServerTest.cs @@ -1566,6 +1566,20 @@ WHERE [e1].[Id] < 5 ORDER BY [e].[Id], [t].[Id]"); } + public override async Task Select_many_over_skip_navigation_where_non_equality(bool async) + { + await base.Select_many_over_skip_navigation_where_non_equality(async); + + AssertSql( + @"SELECT [t].[Id], [t].[CollectionInverseId], [t].[Name], [t].[ReferenceInverseId] +FROM [EntityOnes] AS [e] +LEFT JOIN ( + SELECT [e0].[Id], [e0].[CollectionInverseId], [e0].[Name], [e0].[ReferenceInverseId], [j].[OneId], [j].[TwoId] + FROM [JoinOneToTwo] AS [j] + INNER JOIN [EntityTwos] AS [e0] ON [j].[TwoId] = [e0].[Id] +) AS [t] ON ([e].[Id] = [t].[OneId]) AND ([e].[Id] <> [t].[Id])"); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindIncludeQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindIncludeQuerySqlServerTest.cs index f497718f86a..9666cc8f70f 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindIncludeQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindIncludeQuerySqlServerTest.cs @@ -627,6 +627,24 @@ FROM [Customers] AS [c] ORDER BY [c].[City], [c].[CustomerID], [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] +OUTER APPLY ( + SELECT TOP(5) [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [c].[CustomerID] AS [CustomerID0] + FROM [Orders] AS [o] + WHERE ([o].[CustomerID] <> [c].[CustomerID]) OR [o].[CustomerID] IS NULL + ORDER BY [c].[CustomerID] +) AS [t] +LEFT JOIN [Orders] AS [o0] ON [c].[CustomerID] = [o0].[CustomerID] +WHERE [c].[CustomerID] LIKE N'F%' +ORDER BY [c].[CustomerID], [t].[OrderID], [o0].[OrderID]"); + } + public override async Task Include_collection_on_additional_from_clause2(bool async) { await base.Include_collection_on_additional_from_clause2(async); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs index d8100c5ed11..ebc9f621a0d 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs @@ -3362,6 +3362,33 @@ WHERE [o].[OrderID] > 15000 ORDER BY [t0].[OrderID], [o0].[OrderDate]"); } + public override async Task DefaultIfEmpty_in_subquery_nested_filter_order_comparison(bool async) + { + await base.DefaultIfEmpty_in_subquery_nested_filter_order_comparison(async); + + AssertSql( + @"SELECT [c].[CustomerID], [t0].[OrderID], [t1].[OrderDate] +FROM [Customers] AS [c] +CROSS JOIN ( + SELECT [t].[OrderID], [t].[CustomerID], [t].[EmployeeID], [t].[OrderDate] + FROM ( + SELECT NULL AS [empty] + ) AS [empty] + LEFT JOIN ( + SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] + FROM [Orders] AS [o] + WHERE [o].[OrderID] > 15000 + ) AS [t] ON 1 = 1 +) AS [t0] +OUTER APPLY ( + SELECT [o0].[OrderID], [o0].[CustomerID], [o0].[EmployeeID], [o0].[OrderDate] + FROM [Orders] AS [o0] + WHERE [o0].[OrderID] <= CAST(LEN([c].[CustomerID]) AS int) +) AS [t1] +WHERE ([c].[City] = N'Seattle') AND ([t0].[OrderID] IS NOT NULL AND [t1].[OrderID] IS NOT NULL) +ORDER BY [t0].[OrderID], [t1].[OrderDate]"); + } + public override async Task OrderBy_skip_take(bool async) { await base.OrderBy_skip_take(async); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs index 96f85cfdca1..52c397f74db 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs @@ -1078,6 +1078,50 @@ FROM [Orders] AS [o] ) AS [t]"); } + public override async Task SelectMany_correlated_with_outer_5(bool async) + { + await base.SelectMany_correlated_with_outer_5(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].[City] AS [o] +FROM [Customers] AS [c] +OUTER APPLY ( + SELECT [c].[City], [o].[OrderID] + FROM [Orders] AS [o] + WHERE ([c].[CustomerID] <> [o].[CustomerID]) OR [o].[CustomerID] IS NULL +) AS [t]"); + } + + public override async Task SelectMany_correlated_with_outer_6(bool async) + { + await base.SelectMany_correlated_with_outer_6(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] +OUTER APPLY ( + SELECT TOP(2) [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [c].[City] + FROM [Orders] AS [o] + WHERE ([c].[CustomerID] <> [o].[CustomerID]) OR [o].[CustomerID] IS NULL + ORDER BY [c].[City], [o].[OrderID] +) AS [t]"); + } + + public override async Task SelectMany_correlated_with_outer_7(bool async) + { + await base.SelectMany_correlated_with_outer_7(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] +OUTER APPLY ( + SELECT TOP(2) [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [c].[City] + FROM [Orders] AS [o] + WHERE CAST(LEN([c].[CustomerID]) AS int) >= CAST(LEN([o].[CustomerID]) AS int) + ORDER BY [c].[City], [o].[OrderID] +) AS [t]"); + } + public override async Task FirstOrDefault_over_empty_collection_of_value_type_returns_correct_results(bool async) { await base.FirstOrDefault_over_empty_collection_of_value_type_returns_correct_results(async); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSplitIncludeQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSplitIncludeQuerySqlServerTest.cs index bb4319a1db9..3d1d2281103 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSplitIncludeQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSplitIncludeQuerySqlServerTest.cs @@ -930,6 +930,35 @@ FROM [Customers] AS [c] ORDER BY [c].[City], [c].[CustomerID], [o].[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] +FROM [Customers] AS [c] +OUTER APPLY ( + SELECT TOP(5) [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [c].[CustomerID] AS [CustomerID0] + FROM [Orders] AS [o] + WHERE ([o].[CustomerID] <> [c].[CustomerID]) OR [o].[CustomerID] IS NULL + ORDER BY [c].[CustomerID] +) AS [t] +WHERE [c].[CustomerID] LIKE N'F%' +ORDER BY [c].[CustomerID], [t].[OrderID]", + // + @"SELECT [o0].[OrderID], [o0].[CustomerID], [o0].[EmployeeID], [o0].[OrderDate], [c].[CustomerID], [t].[OrderID] +FROM [Customers] AS [c] +OUTER APPLY ( + SELECT TOP(5) [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [c].[CustomerID] AS [CustomerID0] + FROM [Orders] AS [o] + WHERE ([o].[CustomerID] <> [c].[CustomerID]) OR [o].[CustomerID] IS NULL + ORDER BY [c].[CustomerID] +) AS [t] +INNER JOIN [Orders] AS [o0] ON [c].[CustomerID] = [o0].[CustomerID] +WHERE [c].[CustomerID] LIKE N'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); diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/ComplexNavigationsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/ComplexNavigationsQuerySqliteTest.cs index 2e7e50b8084..63e2f49fbb2 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/ComplexNavigationsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/ComplexNavigationsQuerySqliteTest.cs @@ -20,7 +20,6 @@ public override Task Include_inside_subquery(bool async) } // Sqlite does not support cross/outer apply - public override Task SelectMany_with_outside_reference_to_joined_table_correctly_translated_to_apply(bool async) => null; public override Task Filtered_include_after_different_filtered_include_different_level(bool async) => null; public override void Filtered_include_outer_parameter_used_inside_filter() { } public override Task Filtered_include_and_non_filtered_include_followed_by_then_include_on_same_navigation(bool async) => null; diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/ComplexNavigationsWeakQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/ComplexNavigationsWeakQuerySqliteTest.cs index a94f75e1a47..489aeeb5ea0 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/ComplexNavigationsWeakQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/ComplexNavigationsWeakQuerySqliteTest.cs @@ -15,7 +15,6 @@ public ComplexNavigationsWeakQuerySqliteTest(ComplexNavigationsWeakQuerySqliteFi } // Sqlite does not support cross/outer apply - public override Task SelectMany_with_outside_reference_to_joined_table_correctly_translated_to_apply(bool async) => null; public override Task Filtered_include_after_different_filtered_include_different_level(bool async) => null; public override Task Filtered_include_and_non_filtered_include_followed_by_then_include_on_same_navigation(bool async) => null; public override Task Filtered_include_complex_three_level_with_middle_having_filter1(bool async) => null; diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs index 8768e01e8cb..1a48a3d79bb 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs @@ -77,6 +77,7 @@ public override Task DateTimeOffset_Date_returns_datetime(bool async) public override Task Subquery_projecting_non_nullable_scalar_contains_non_nullable_value_doesnt_need_null_expansion(bool async) => null; public override Task Subquery_projecting_non_nullable_scalar_contains_non_nullable_value_doesnt_need_null_expansion_negated(bool async) => null; + public override Task SelectMany_predicate_with_non_equality_comparison_with_Take_doesnt_convert_to_join(bool async) => null; public override async Task Negate_on_binary_expression(bool async) { diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/ManyToManyQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/ManyToManyQuerySqliteTest.cs index 09e5b9f13aa..4901560dced 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/ManyToManyQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/ManyToManyQuerySqliteTest.cs @@ -16,7 +16,6 @@ public ManyToManyQuerySqliteTest(ManyToManyQuerySqliteFixture fixture) // Sqlite does not support Apply operations public override Task Skip_navigation_order_by_single_or_default(bool async) => Task.CompletedTask; - public override Task Filtered_include_skip_navigation_order_by_skip_take_then_include_skip_navigation_where(bool async) => Task.CompletedTask; [ConditionalTheory(Skip = "Issue#21541")] diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindIncludeNoTrackingQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindIncludeNoTrackingQuerySqliteTest.cs index 8be2b128216..172d3b11e3b 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindIncludeNoTrackingQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindIncludeNoTrackingQuerySqliteTest.cs @@ -21,5 +21,7 @@ public NorthwindIncludeNoTrackingQuerySqliteTest(NorthwindQuerySqliteFixture Task.CompletedTask; public override Task Filtered_include_with_multiple_ordering(bool async) => Task.CompletedTask; + + public override Task Include_collection_with_outer_apply_with_filter_non_equality(bool async) => Task.CompletedTask; } } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindIncludeQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindIncludeQuerySqliteTest.cs index 35f596081e5..6dedeb1937d 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindIncludeQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindIncludeQuerySqliteTest.cs @@ -21,5 +21,6 @@ public NorthwindIncludeQuerySqliteTest(NorthwindQuerySqliteFixture Task.CompletedTask; public override Task Filtered_include_with_multiple_ordering(bool async) => Task.CompletedTask; + public override Task Include_collection_with_outer_apply_with_filter_non_equality(bool async) => Task.CompletedTask; } } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindMiscellaneousQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindMiscellaneousQuerySqliteTest.cs index 354177e998d..9191706ead1 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindMiscellaneousQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindMiscellaneousQuerySqliteTest.cs @@ -277,6 +277,10 @@ public override async Task Concat_constant_string_int(bool async) FROM ""Orders"" AS ""o"""); } + + // Sqlite does not support Apply operations + public override Task DefaultIfEmpty_in_subquery_nested_filter_order_comparison(bool async) => Task.CompletedTask; + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindSelectQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindSelectQuerySqliteTest.cs index 7f78046db24..51d7963dfaa 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindSelectQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindSelectQuerySqliteTest.cs @@ -144,6 +144,12 @@ public override Task Project_single_element_from_collection_with_multiple_OrderB public override Task SelectMany_correlated_with_outer_4(bool async) => null; + public override Task SelectMany_correlated_with_outer_5(bool async) => null; + + public override Task SelectMany_correlated_with_outer_6(bool async) => null; + + public override Task SelectMany_correlated_with_outer_7(bool async) => null; + public override Task SelectMany_whose_selector_references_outer_source(bool async) => null; [ConditionalTheory(Skip = "Issue#17324")] diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindSplitIncludeNoTrackingQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindSplitIncludeNoTrackingQuerySqliteTest.cs index b85faa80cab..f481b4e4ddb 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindSplitIncludeNoTrackingQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindSplitIncludeNoTrackingQuerySqliteTest.cs @@ -21,5 +21,7 @@ public NorthwindSplitIncludeNoTrackingQuerySqliteTest(NorthwindQuerySqliteFixtur public override Task Include_collection_with_outer_apply_with_filter(bool async) => Task.CompletedTask; public override Task Filtered_include_with_multiple_ordering(bool async) => Task.CompletedTask; + + public override Task Include_collection_with_outer_apply_with_filter_non_equality(bool async) => Task.CompletedTask; } } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindSplitIncludeQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindSplitIncludeQuerySqliteTest.cs index 26ef3d3cfec..603b3816dbc 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindSplitIncludeQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindSplitIncludeQuerySqliteTest.cs @@ -21,5 +21,6 @@ public NorthwindSplitIncludeQuerySqliteTest(NorthwindQuerySqliteFixture Task.CompletedTask; public override Task Filtered_include_with_multiple_ordering(bool async) => Task.CompletedTask; + public override Task Include_collection_with_outer_apply_with_filter_non_equality(bool async) => Task.CompletedTask; } } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindStringIncludeQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindStringIncludeQuerySqliteTest.cs index 9b4d48131ff..e7d94dd9eec 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindStringIncludeQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindStringIncludeQuerySqliteTest.cs @@ -21,5 +21,8 @@ public NorthwindStringIncludeQuerySqliteTest(NorthwindQuerySqliteFixture Task.CompletedTask; public override Task Filtered_include_with_multiple_ordering(bool async) => Task.CompletedTask; + public override Task Include_collection_with_outer_apply_with_filter_non_equality(bool async) => Task.CompletedTask; + + } } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/TPTGearsOfWarQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/TPTGearsOfWarQuerySqliteTest.cs index 6b07bcb699b..78eb275dcf4 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/TPTGearsOfWarQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/TPTGearsOfWarQuerySqliteTest.cs @@ -78,6 +78,8 @@ public override Task DateTimeOffset_Date_returns_datetime(bool async) public override Task Subquery_projecting_non_nullable_scalar_contains_non_nullable_value_doesnt_need_null_expansion_negated(bool async) => null; + public override Task SelectMany_predicate_with_non_equality_comparison_with_Take_doesnt_convert_to_join(bool async) => null; + public override async Task Negate_on_binary_expression(bool async) { await base.Negate_on_binary_expression(async);