From 1c29984574a0219264e60cf5e801127964c0cc75 Mon Sep 17 00:00:00 2001 From: Christopher Jolly Date: Sun, 19 Nov 2023 18:01:03 +0800 Subject: [PATCH] Improve handling of scalar subqueries in order by clause (#177) * Add expression visitor to locate a scalar subquery. Handles finding deeper subqueries better than original code. Also handle the case where the expression can be regarded as scalar (i.e. has a TOP 1 and projects only one field). In that case we rewrite the projections so that we take out any previously added projections as it is clear we are not needing it higher up in the SQL --- .../Internal/JetLiftOrderByPostprocessor.cs | 52 +-- .../JetLocateScalarSubqueryVisitor.cs | 413 ++++++++++++++++++ .../GreenTests/ace_2010_odbc_x86.txt | 2 + .../GreenTests/ace_2010_oledb_x86.txt | 14 + .../Query/GearsOfWarQueryJetTest.cs | 16 +- .../NorthwindMiscellaneousQueryJetTest.cs | 42 +- .../Query/TPCGearsOfWarQueryJetTest.cs | 32 +- .../Query/TPTGearsOfWarQueryJetTest.cs | 22 +- 8 files changed, 508 insertions(+), 85 deletions(-) create mode 100644 src/EFCore.Jet/Query/Internal/JetLocateScalarSubqueryVisitor.cs diff --git a/src/EFCore.Jet/Query/Internal/JetLiftOrderByPostprocessor.cs b/src/EFCore.Jet/Query/Internal/JetLiftOrderByPostprocessor.cs index 568be87..2bbf3b4 100644 --- a/src/EFCore.Jet/Query/Internal/JetLiftOrderByPostprocessor.cs +++ b/src/EFCore.Jet/Query/Internal/JetLiftOrderByPostprocessor.cs @@ -1,14 +1,8 @@ -using System; using System.Collections.Generic; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; -using EntityFrameworkCore.Jet.Utilities; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Query; -using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; using Microsoft.EntityFrameworkCore.Storage; @@ -65,14 +59,15 @@ public class JetLiftOrderByPostprocessor : ExpressionVisitor case SelectExpression selectExpression: { Dictionary columnsToRewrite = new(); - + bool isscalarselect = selectExpression is { Limit: SqlConstantExpression { Value: 1 }, Projection.Count: 1 }; for (int i = 0; i < selectExpression.Orderings.Count; i++) { var sqlExpression = selectExpression.Orderings[i].Expression; if (sqlExpression is not ColumnExpression) { - bool containsscalar = sqlExpression is ScalarSubqueryExpression; - containsscalar = containsscalar || TryPeekFunction(sqlExpression as SqlFunctionExpression) || TryPeekCase(sqlExpression as CaseExpression) || TryPeekExists(sqlExpression as ExistsExpression); + var locate = new JetLocateScalarSubqueryVisitor(_typeMappingSource, _sqlExpressionFactory); + var locatedExpression = locate.Visit(sqlExpression); + bool containsscalar = locatedExpression is ScalarSubqueryExpression or ExistsExpression; if (containsscalar) { int index = selectExpression.AddToProjection(sqlExpression); @@ -134,6 +129,25 @@ public class JetLiftOrderByPostprocessor : ExpressionVisitor selectExpression.AppendOrdering(new OrderingExpression(newcolexp, ascending)); } } + + if (isscalarselect) + { + List newProjections = new List(); + for (int j = 0; j < selectExpression.Projection.Count; j++) + { + var proj = selectExpression.Projection[j]; + var item = columnsToRewrite.SingleOrDefault(c => c.Value.indexcol == j); + if (item.Value.indexcol == null) + { + newProjections.Add(proj); + } + + } + + selectExpression = selectExpression.Update(newProjections, selectExpression.Tables, selectExpression.Predicate, + selectExpression.GroupBy, selectExpression.Having, selectExpression.Orderings, + selectExpression.Limit, selectExpression.Offset); + } var result = base.Visit(selectExpression); return result; } @@ -141,24 +155,4 @@ public class JetLiftOrderByPostprocessor : ExpressionVisitor return base.Visit(expression); } - - private bool TryPeekExists(ExistsExpression? existsExpression) - { - return existsExpression is not null; - } - - private bool TryPeekCase(CaseExpression? sqlExpression) - { - if (sqlExpression is null) return false; - if (sqlExpression.ElseResult is ScalarSubqueryExpression) return true; - if (sqlExpression.WhenClauses.Any(x => x.Result is ScalarSubqueryExpression)) return true; - if (sqlExpression.Operand is ScalarSubqueryExpression) return true; - return false; - } - - private bool TryPeekFunction(SqlFunctionExpression? sqlExpression) - { - if (sqlExpression is null) return false; - return sqlExpression.Arguments != null && sqlExpression.Arguments.Any(x => x is ScalarSubqueryExpression); - } } diff --git a/src/EFCore.Jet/Query/Internal/JetLocateScalarSubqueryVisitor.cs b/src/EFCore.Jet/Query/Internal/JetLocateScalarSubqueryVisitor.cs new file mode 100644 index 0000000..2691a6a --- /dev/null +++ b/src/EFCore.Jet/Query/Internal/JetLocateScalarSubqueryVisitor.cs @@ -0,0 +1,413 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Linq.Expressions; +using EntityFrameworkCore.Jet.Utilities; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Query.Internal; +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using Microsoft.EntityFrameworkCore.Storage; + +namespace EntityFrameworkCore.Jet.Query.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 JetLocateScalarSubqueryVisitor : SqlExpressionVisitor +{ + private readonly IRelationalTypeMappingSource _typeMappingSource; + private readonly ISqlExpressionFactory _sqlExpressionFactory; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public JetLocateScalarSubqueryVisitor( + IRelationalTypeMappingSource typeMappingSource, + ISqlExpressionFactory sqlExpressionFactory) + { + (_typeMappingSource, _sqlExpressionFactory) = (typeMappingSource, sqlExpressionFactory); + } + + protected override Expression VisitAtTimeZone(AtTimeZoneExpression atTimeZoneExpression) + { + var operand = (SqlExpression)Visit(atTimeZoneExpression.Operand); + var timeZone = (SqlExpression)Visit(atTimeZoneExpression.TimeZone); + return atTimeZoneExpression.Update(operand, timeZone); + } + + protected override Expression VisitCase(CaseExpression caseExpression) + { + var operand = (SqlExpression?)Visit(caseExpression.Operand); + var whenClauses = new List(); + foreach (var whenClause in caseExpression.WhenClauses) + { + var test = (SqlExpression)Visit(whenClause.Test); + var result = (SqlExpression)Visit(whenClause.Result); + whenClauses.Add(new CaseWhenClause(test, result)); + } + var elseResult = (SqlExpression?)Visit(caseExpression.ElseResult); + + return caseExpression.Update(operand, whenClauses, elseResult); + } + + protected override Expression VisitCollate(CollateExpression collateExpression) + { + var operand = (SqlExpression)Visit(collateExpression.Operand); + + return collateExpression.Update(operand); + } + + protected override Expression VisitColumn(ColumnExpression columnExpression) + { + return columnExpression; + } + + protected override Expression VisitCrossApply(CrossApplyExpression crossApplyExpression) + { + var table = (TableExpressionBase)Visit(crossApplyExpression.Table); + return crossApplyExpression.Update(table); + } + + protected override Expression VisitCrossJoin(CrossJoinExpression crossJoinExpression) + { + var table = (TableExpressionBase)Visit(crossJoinExpression.Table); + return crossJoinExpression.Update(table); + } + + protected override Expression VisitDelete(DeleteExpression deleteExpression) + { + return deleteExpression.Update((SelectExpression)Visit(deleteExpression.SelectExpression)); + } + + protected override Expression VisitDistinct(DistinctExpression distinctExpression) + { + var operand = (SqlExpression)Visit(distinctExpression.Operand); + return distinctExpression.Update(operand); + } + + protected override Expression VisitExcept(ExceptExpression exceptExpression) + { + var source1 = (SelectExpression)Visit(exceptExpression.Source1); + var source2 = (SelectExpression)Visit(exceptExpression.Source2); + return exceptExpression.Update(source1, source2); + } + + protected override Expression VisitExists(ExistsExpression existsExpression) + { + var subquery = (SelectExpression)Visit(existsExpression.Subquery); + + return existsExpression.Update(subquery); + } + + protected override Expression VisitFromSql(FromSqlExpression fromSqlExpression) + { + return fromSqlExpression; + } + + protected override Expression VisitIn(InExpression inExpression) + { + var item = (SqlExpression)Visit(inExpression.Item); + var subquery = (SelectExpression?)Visit(inExpression.Subquery); + + var values = inExpression.Values; + SqlExpression[]? newValues = null; + if (values is not null) + { + for (var i = 0; i < values.Count; i++) + { + var value = values[i]; + var newValue = (SqlExpression)Visit(value); + + if (newValue != value && newValues is null) + { + newValues = new SqlExpression[values.Count]; + for (var j = 0; j < i; j++) + { + newValues[j] = values[j]; + } + } + + if (newValues is not null) + { + newValues[i] = newValue; + } + } + } + + var valuesParameter = (SqlParameterExpression?)Visit(inExpression.ValuesParameter); + return inExpression.Update(item, subquery, newValues ?? values, valuesParameter); + } + + protected override Expression VisitIntersect(IntersectExpression intersectExpression) + { + var source1 = (SelectExpression)Visit(intersectExpression.Source1); + var source2 = (SelectExpression)Visit(intersectExpression.Source2); + return intersectExpression.Update(source1, source2); + } + + protected override Expression VisitLike(LikeExpression likeExpression) + { + var match = (SqlExpression)Visit(likeExpression.Match); + var pattern = (SqlExpression)Visit(likeExpression.Pattern); + var escapeChar = (SqlExpression?)Visit(likeExpression.EscapeChar); + + return likeExpression.Update(match, pattern, escapeChar); + } + + protected override Expression VisitInnerJoin(InnerJoinExpression innerJoinExpression) + { + var table = (TableExpressionBase)Visit(innerJoinExpression.Table); + var joinPredicate = (SqlExpression)Visit(innerJoinExpression.JoinPredicate); + return innerJoinExpression.Update(table, joinPredicate); + } + + protected override Expression VisitLeftJoin(LeftJoinExpression leftJoinExpression) + { + var table = (TableExpressionBase)Visit(leftJoinExpression.Table); + var joinPredicate = (SqlExpression)Visit(leftJoinExpression.JoinPredicate); + return leftJoinExpression.Update(table, joinPredicate); + } + + protected override Expression VisitOrdering(OrderingExpression orderingExpression) + { + var expression = (SqlExpression)Visit(orderingExpression.Expression); + return orderingExpression.Update(expression); + } + + protected override Expression VisitOuterApply(OuterApplyExpression outerApplyExpression) + { + var table = (TableExpressionBase)Visit(outerApplyExpression.Table); + return outerApplyExpression.Update(table); + } + + protected override Expression VisitProjection(ProjectionExpression projectionExpression) + { + var expression = (SqlExpression)Visit(projectionExpression.Expression); + + return projectionExpression.Update(expression); + } + + protected override Expression VisitTableValuedFunction(TableValuedFunctionExpression tableValuedFunctionExpression) + { + var arguments = new SqlExpression[tableValuedFunctionExpression.Arguments.Count]; + for (var i = 0; i < arguments.Length; i++) + { + arguments[i] = (SqlExpression)Visit(tableValuedFunctionExpression.Arguments[i]); + } + + return tableValuedFunctionExpression.Update(arguments); + } + + protected override Expression VisitRowNumber(RowNumberExpression rowNumberExpression) + { + var partitions = new List(); + foreach (var partition in rowNumberExpression.Partitions) + { + var newPartition = (SqlExpression)Visit(partition); + partitions.Add(newPartition); + } + + var orderings = new List(); + foreach (var ordering in rowNumberExpression.Orderings) + { + var newOrdering = (OrderingExpression)Visit(ordering); + orderings.Add(newOrdering); + } + return rowNumberExpression.Update(partitions, orderings); + } + + protected override Expression VisitRowValue(RowValueExpression rowValueExpression) + { + var values = new SqlExpression[rowValueExpression.Values.Count]; + for (var i = 0; i < values.Length; i++) + { + values[i] = (SqlExpression)Visit(rowValueExpression.Values[i]); + } + return rowValueExpression.Update(values); + } + + protected override Expression VisitScalarSubquery(ScalarSubqueryExpression scalarSubqueryExpression) + { + return scalarSubqueryExpression; + } + + protected override Expression VisitSelect(SelectExpression selectExpression) + { + var changed = false; + var projections = new List(); + foreach (var item in selectExpression.Projection) + { + var updatedProjection = (ProjectionExpression)Visit(item); + projections.Add(updatedProjection); + changed |= updatedProjection != item; + } + + var tables = new List(); + foreach (var table in selectExpression.Tables) + { + var newTable = (TableExpressionBase)Visit(table); + changed |= newTable != table; + tables.Add(newTable); + } + + var predicate = (SqlExpression?)Visit(selectExpression.Predicate); + changed |= predicate != selectExpression.Predicate; + + var groupBy = new List(); + foreach (var groupingKey in selectExpression.GroupBy) + { + var newGroupingKey = (SqlExpression)Visit(groupingKey); + changed |= newGroupingKey != groupingKey; + groupBy.Add(newGroupingKey); + } + + var havingExpression = (SqlExpression?)Visit(selectExpression.Having); + changed |= havingExpression != selectExpression.Having; + + var orderings = new List(); + foreach (var ordering in selectExpression.Orderings) + { + var orderingExpression = (SqlExpression)Visit(ordering.Expression); + changed |= orderingExpression != ordering.Expression; + orderings.Add(ordering.Update(orderingExpression)); + } + + var offset = (SqlExpression?)Visit(selectExpression.Offset); + changed |= offset != selectExpression.Offset; + + var limit = (SqlExpression?)Visit(selectExpression.Limit); + changed |= limit != selectExpression.Limit; + + return changed + ? selectExpression.Update( + projections, tables, predicate, groupBy, havingExpression, orderings, limit, offset) + : selectExpression; + } + + protected override Expression VisitSqlBinary(SqlBinaryExpression sqlBinaryExpression) + { + var newLeft = (SqlExpression)Visit(sqlBinaryExpression.Left); + var newRight = (SqlExpression)Visit(sqlBinaryExpression.Right); + if (newLeft is ScalarSubqueryExpression) + { + return newLeft; + } + + if (newRight is ScalarSubqueryExpression) + { + return newRight; + } + sqlBinaryExpression = sqlBinaryExpression.Update(newLeft, newRight); + return sqlBinaryExpression; + } + + protected override Expression VisitSqlConstant(SqlConstantExpression sqlConstantExpression) + { + return sqlConstantExpression; + } + + protected override Expression VisitSqlFragment(SqlFragmentExpression sqlFragmentExpression) + { + return sqlFragmentExpression; + } + + protected override Expression VisitSqlFunction(SqlFunctionExpression sqlFunctionExpression) + { + var instance = (SqlExpression?)Visit(sqlFunctionExpression.Instance); + SqlExpression[]? arguments = default; + if (!sqlFunctionExpression.IsNiladic) + { + arguments = new SqlExpression[sqlFunctionExpression.Arguments.Count]; + for (var i = 0; i < arguments.Length; i++) + { + arguments[i] = (SqlExpression)Visit(sqlFunctionExpression.Arguments[i]); + if (arguments[i] is ScalarSubqueryExpression) + { + return arguments[i]; + } + } + } + var newFunction = sqlFunctionExpression.Update(instance, arguments); + + return newFunction; + } + + protected override Expression VisitSqlParameter(SqlParameterExpression sqlParameterExpression) + { + return sqlParameterExpression; + } + + protected override Expression VisitSqlUnary(SqlUnaryExpression sqlUnaryExpression) + { + var operand = (SqlExpression)Visit(sqlUnaryExpression.Operand); + if (operand is ScalarSubqueryExpression) + { + return operand; + } + return sqlUnaryExpression.Update(operand); + } + + protected override Expression VisitTable(TableExpression tableExpression) + { + return tableExpression; + } + + protected override Expression VisitUnion(UnionExpression unionExpression) + { + var source1 = (SelectExpression)Visit(unionExpression.Source1); + var source2 = (SelectExpression)Visit(unionExpression.Source2); + return unionExpression.Update(source1, source2); + } + + protected override Expression VisitUpdate(UpdateExpression updateExpression) + { + var selectExpression = (SelectExpression)Visit(updateExpression.SelectExpression); + 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)); + } + } + + return updateExpression.Update(selectExpression, columnValueSetters ?? updateExpression.ColumnValueSetters); + } + + protected override Expression VisitJsonScalar(JsonScalarExpression jsonScalarExpression) + { + return jsonScalarExpression; + } + + protected override Expression VisitValues(ValuesExpression valuesExpression) + { + var rowValues = new RowValueExpression[valuesExpression.RowValues.Count]; + for (var i = 0; i < rowValues.Length; i++) + { + rowValues[i] = (RowValueExpression)Visit(valuesExpression.RowValues[i]); + } + return valuesExpression.Update(rowValues); + } +} diff --git a/test/EFCore.Jet.FunctionalTests/GreenTests/ace_2010_odbc_x86.txt b/test/EFCore.Jet.FunctionalTests/GreenTests/ace_2010_odbc_x86.txt index a873f07..2db7d82 100644 --- a/test/EFCore.Jet.FunctionalTests/GreenTests/ace_2010_odbc_x86.txt +++ b/test/EFCore.Jet.FunctionalTests/GreenTests/ace_2010_odbc_x86.txt @@ -10279,6 +10279,8 @@ EntityFrameworkCore.Jet.FunctionalTests.Query.NorthwindMiscellaneousQueryJetTest EntityFrameworkCore.Jet.FunctionalTests.Query.NorthwindMiscellaneousQueryJetTest.OrderBy_conditional_operator(isAsync: True) EntityFrameworkCore.Jet.FunctionalTests.Query.NorthwindMiscellaneousQueryJetTest.OrderBy_correlated_subquery1(isAsync: False) EntityFrameworkCore.Jet.FunctionalTests.Query.NorthwindMiscellaneousQueryJetTest.OrderBy_correlated_subquery1(isAsync: True) +EntityFrameworkCore.Jet.FunctionalTests.Query.NorthwindMiscellaneousQueryJetTest.OrderBy_correlated_subquery2(isAsync: False) +EntityFrameworkCore.Jet.FunctionalTests.Query.NorthwindMiscellaneousQueryJetTest.OrderBy_correlated_subquery2(isAsync: True) EntityFrameworkCore.Jet.FunctionalTests.Query.NorthwindMiscellaneousQueryJetTest.OrderBy_Dto_projection_skip_take(isAsync: False) EntityFrameworkCore.Jet.FunctionalTests.Query.NorthwindMiscellaneousQueryJetTest.OrderBy_Dto_projection_skip_take(isAsync: True) EntityFrameworkCore.Jet.FunctionalTests.Query.NorthwindMiscellaneousQueryJetTest.OrderBy_empty_list_contains(isAsync: False) diff --git a/test/EFCore.Jet.FunctionalTests/GreenTests/ace_2010_oledb_x86.txt b/test/EFCore.Jet.FunctionalTests/GreenTests/ace_2010_oledb_x86.txt index 8ad1d7c..cb5e3ee 100644 --- a/test/EFCore.Jet.FunctionalTests/GreenTests/ace_2010_oledb_x86.txt +++ b/test/EFCore.Jet.FunctionalTests/GreenTests/ace_2010_oledb_x86.txt @@ -10513,8 +10513,12 @@ EntityFrameworkCore.Jet.FunctionalTests.Query.GearsOfWarQueryJetTest.Where_subqu EntityFrameworkCore.Jet.FunctionalTests.Query.GearsOfWarQueryJetTest.Where_subquery_boolean(isAsync: True) EntityFrameworkCore.Jet.FunctionalTests.Query.GearsOfWarQueryJetTest.Where_subquery_distinct_singleordefault_boolean2(isAsync: False) EntityFrameworkCore.Jet.FunctionalTests.Query.GearsOfWarQueryJetTest.Where_subquery_distinct_singleordefault_boolean2(isAsync: True) +EntityFrameworkCore.Jet.FunctionalTests.Query.GearsOfWarQueryJetTest.Where_subquery_equality_to_null_with_composite_key_should_match_nulls(async: False) +EntityFrameworkCore.Jet.FunctionalTests.Query.GearsOfWarQueryJetTest.Where_subquery_equality_to_null_with_composite_key_should_match_nulls(async: True) EntityFrameworkCore.Jet.FunctionalTests.Query.GearsOfWarQueryJetTest.Where_subquery_equality_to_null_with_composite_key(async: False) EntityFrameworkCore.Jet.FunctionalTests.Query.GearsOfWarQueryJetTest.Where_subquery_equality_to_null_with_composite_key(async: True) +EntityFrameworkCore.Jet.FunctionalTests.Query.GearsOfWarQueryJetTest.Where_subquery_equality_to_null_without_composite_key_should_match_null(async: False) +EntityFrameworkCore.Jet.FunctionalTests.Query.GearsOfWarQueryJetTest.Where_subquery_equality_to_null_without_composite_key_should_match_null(async: True) EntityFrameworkCore.Jet.FunctionalTests.Query.GearsOfWarQueryJetTest.Where_subquery_equality_to_null_without_composite_key(async: False) EntityFrameworkCore.Jet.FunctionalTests.Query.GearsOfWarQueryJetTest.Where_subquery_equality_to_null_without_composite_key(async: True) EntityFrameworkCore.Jet.FunctionalTests.Query.GearsOfWarQueryJetTest.Where_TimeOnly_Add_TimeSpan(async: False) @@ -13138,6 +13142,8 @@ EntityFrameworkCore.Jet.FunctionalTests.Query.NorthwindMiscellaneousQueryJetTest EntityFrameworkCore.Jet.FunctionalTests.Query.NorthwindMiscellaneousQueryJetTest.OrderBy_conditional_operator(isAsync: True) EntityFrameworkCore.Jet.FunctionalTests.Query.NorthwindMiscellaneousQueryJetTest.OrderBy_correlated_subquery1(isAsync: False) EntityFrameworkCore.Jet.FunctionalTests.Query.NorthwindMiscellaneousQueryJetTest.OrderBy_correlated_subquery1(isAsync: True) +EntityFrameworkCore.Jet.FunctionalTests.Query.NorthwindMiscellaneousQueryJetTest.OrderBy_correlated_subquery2(isAsync: False) +EntityFrameworkCore.Jet.FunctionalTests.Query.NorthwindMiscellaneousQueryJetTest.OrderBy_correlated_subquery2(isAsync: True) EntityFrameworkCore.Jet.FunctionalTests.Query.NorthwindMiscellaneousQueryJetTest.OrderBy_Dto_projection_skip_take(isAsync: False) EntityFrameworkCore.Jet.FunctionalTests.Query.NorthwindMiscellaneousQueryJetTest.OrderBy_Dto_projection_skip_take(isAsync: True) EntityFrameworkCore.Jet.FunctionalTests.Query.NorthwindMiscellaneousQueryJetTest.OrderBy_empty_list_contains(isAsync: False) @@ -17115,8 +17121,12 @@ EntityFrameworkCore.Jet.FunctionalTests.Query.TPCGearsOfWarQueryJetTest.Where_su EntityFrameworkCore.Jet.FunctionalTests.Query.TPCGearsOfWarQueryJetTest.Where_subquery_boolean(async: True) EntityFrameworkCore.Jet.FunctionalTests.Query.TPCGearsOfWarQueryJetTest.Where_subquery_distinct_singleordefault_boolean2(async: False) EntityFrameworkCore.Jet.FunctionalTests.Query.TPCGearsOfWarQueryJetTest.Where_subquery_distinct_singleordefault_boolean2(async: True) +EntityFrameworkCore.Jet.FunctionalTests.Query.TPCGearsOfWarQueryJetTest.Where_subquery_equality_to_null_with_composite_key_should_match_nulls(async: False) +EntityFrameworkCore.Jet.FunctionalTests.Query.TPCGearsOfWarQueryJetTest.Where_subquery_equality_to_null_with_composite_key_should_match_nulls(async: True) EntityFrameworkCore.Jet.FunctionalTests.Query.TPCGearsOfWarQueryJetTest.Where_subquery_equality_to_null_with_composite_key(async: False) EntityFrameworkCore.Jet.FunctionalTests.Query.TPCGearsOfWarQueryJetTest.Where_subquery_equality_to_null_with_composite_key(async: True) +EntityFrameworkCore.Jet.FunctionalTests.Query.TPCGearsOfWarQueryJetTest.Where_subquery_equality_to_null_without_composite_key_should_match_null(async: False) +EntityFrameworkCore.Jet.FunctionalTests.Query.TPCGearsOfWarQueryJetTest.Where_subquery_equality_to_null_without_composite_key_should_match_null(async: True) EntityFrameworkCore.Jet.FunctionalTests.Query.TPCGearsOfWarQueryJetTest.Where_subquery_equality_to_null_without_composite_key(async: False) EntityFrameworkCore.Jet.FunctionalTests.Query.TPCGearsOfWarQueryJetTest.Where_subquery_equality_to_null_without_composite_key(async: True) EntityFrameworkCore.Jet.FunctionalTests.Query.TPCGearsOfWarQueryJetTest.Where_TimeOnly_Add_TimeSpan(async: False) @@ -18739,8 +18749,12 @@ EntityFrameworkCore.Jet.FunctionalTests.Query.TPTGearsOfWarQueryJetTest.Where_su EntityFrameworkCore.Jet.FunctionalTests.Query.TPTGearsOfWarQueryJetTest.Where_subquery_boolean(async: True) EntityFrameworkCore.Jet.FunctionalTests.Query.TPTGearsOfWarQueryJetTest.Where_subquery_distinct_singleordefault_boolean2(async: False) EntityFrameworkCore.Jet.FunctionalTests.Query.TPTGearsOfWarQueryJetTest.Where_subquery_distinct_singleordefault_boolean2(async: True) +EntityFrameworkCore.Jet.FunctionalTests.Query.TPTGearsOfWarQueryJetTest.Where_subquery_equality_to_null_with_composite_key_should_match_nulls(async: False) +EntityFrameworkCore.Jet.FunctionalTests.Query.TPTGearsOfWarQueryJetTest.Where_subquery_equality_to_null_with_composite_key_should_match_nulls(async: True) EntityFrameworkCore.Jet.FunctionalTests.Query.TPTGearsOfWarQueryJetTest.Where_subquery_equality_to_null_with_composite_key(async: False) EntityFrameworkCore.Jet.FunctionalTests.Query.TPTGearsOfWarQueryJetTest.Where_subquery_equality_to_null_with_composite_key(async: True) +EntityFrameworkCore.Jet.FunctionalTests.Query.TPTGearsOfWarQueryJetTest.Where_subquery_equality_to_null_without_composite_key_should_match_null(async: False) +EntityFrameworkCore.Jet.FunctionalTests.Query.TPTGearsOfWarQueryJetTest.Where_subquery_equality_to_null_without_composite_key_should_match_null(async: True) EntityFrameworkCore.Jet.FunctionalTests.Query.TPTGearsOfWarQueryJetTest.Where_subquery_equality_to_null_without_composite_key(async: False) EntityFrameworkCore.Jet.FunctionalTests.Query.TPTGearsOfWarQueryJetTest.Where_subquery_equality_to_null_without_composite_key(async: True) EntityFrameworkCore.Jet.FunctionalTests.Query.TPTGearsOfWarQueryJetTest.Where_TimeOnly_Add_TimeSpan(async: False) diff --git a/test/EFCore.Jet.FunctionalTests/Query/GearsOfWarQueryJetTest.cs b/test/EFCore.Jet.FunctionalTests/Query/GearsOfWarQueryJetTest.cs index 25f3e66..b7b938c 100644 --- a/test/EFCore.Jet.FunctionalTests/Query/GearsOfWarQueryJetTest.cs +++ b/test/EFCore.Jet.FunctionalTests/Query/GearsOfWarQueryJetTest.cs @@ -9352,12 +9352,12 @@ WHERE NOT EXISTS ( AssertSql( """ -SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] -FROM [Squads] AS [s] +SELECT `s`.`Id`, `s`.`Banner`, `s`.`Banner5`, `s`.`InternalNumber`, `s`.`Name` +FROM `Squads` AS `s` WHERE NOT EXISTS ( SELECT 1 - FROM [Gears] AS [g] - WHERE [s].[Id] = [g].[SquadId] AND [g].[FullName] = N'Anthony Carmine') + FROM `Gears` AS `g` + WHERE `s`.`Id` = `g`.`SquadId` AND `g`.`FullName` = 'Anthony Carmine') """); } @@ -9382,12 +9382,12 @@ WHERE NOT EXISTS ( AssertSql( """ -SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] -FROM [Gears] AS [g] +SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`Discriminator`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank` +FROM `Gears` AS `g` WHERE NOT EXISTS ( SELECT 1 - FROM [Weapons] AS [w] - WHERE [g].[FullName] = [w].[OwnerFullName] AND [w].[Name] = N'Hammer of Dawn') + FROM `Weapons` AS `w` + WHERE `g`.`FullName` = `w`.`OwnerFullName` AND `w`.`Name` = 'Hammer of Dawn') """); } diff --git a/test/EFCore.Jet.FunctionalTests/Query/NorthwindMiscellaneousQueryJetTest.cs b/test/EFCore.Jet.FunctionalTests/Query/NorthwindMiscellaneousQueryJetTest.cs index acb55f2..d92c0cc 100644 --- a/test/EFCore.Jet.FunctionalTests/Query/NorthwindMiscellaneousQueryJetTest.cs +++ b/test/EFCore.Jet.FunctionalTests/Query/NorthwindMiscellaneousQueryJetTest.cs @@ -2283,27 +2283,29 @@ ORDER BY NOT (`t`.`c`), `t`.`CustomerID` await base.OrderBy_correlated_subquery2(isAsync); AssertSql( - $@"SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate` + """ +SELECT `o`.`OrderID`, `o`.`CustomerID`, `o`.`EmployeeID`, `o`.`OrderDate` FROM `Orders` AS `o` -WHERE (`o`.`OrderID` <= 10250) AND ((( - SELECT TOP 1 `c`.`City` - FROM `Customers` AS `c` - ORDER BY CASE - WHEN EXISTS ( - SELECT 1 - FROM `Customers` AS `c0` - WHERE `c0`.`CustomerID` = 'ALFKI') THEN True - ELSE False - END) <> 'Nowhere') OR ( - SELECT TOP 1 `c`.`City` - FROM `Customers` AS `c` - ORDER BY CASE - WHEN EXISTS ( - SELECT 1 - FROM `Customers` AS `c0` - WHERE `c0`.`CustomerID` = 'ALFKI') THEN True - ELSE False - END) IS NULL)"); +WHERE `o`.`OrderID` <= 10250 AND (( + SELECT `t`.`City` + FROM ( + SELECT TOP 1 `c`.`City`, IIF(EXISTS ( + SELECT 1 + FROM `Customers` AS `c1` + WHERE `c1`.`CustomerID` = 'ALFKI'), TRUE, FALSE) AS `c`, `c`.`CustomerID` + FROM `Customers` AS `c` + ) AS `t` + ORDER BY NOT (`t`.`c`)) <> 'Nowhere' OR ( + SELECT `t`.`City` + FROM ( + SELECT TOP 1 `c`.`City`, IIF(EXISTS ( + SELECT 1 + FROM `Customers` AS `c1` + WHERE `c1`.`CustomerID` = 'ALFKI'), TRUE, FALSE) AS `c`, `c`.`CustomerID` + FROM `Customers` AS `c` + ) AS `t` + ORDER BY NOT (`t`.`c`)) IS NULL) +"""); } public override async Task Where_subquery_recursive_trivial(bool isAsync) diff --git a/test/EFCore.Jet.FunctionalTests/Query/TPCGearsOfWarQueryJetTest.cs b/test/EFCore.Jet.FunctionalTests/Query/TPCGearsOfWarQueryJetTest.cs index 9fdb2b3..93fb93b 100644 --- a/test/EFCore.Jet.FunctionalTests/Query/TPCGearsOfWarQueryJetTest.cs +++ b/test/EFCore.Jet.FunctionalTests/Query/TPCGearsOfWarQueryJetTest.cs @@ -12792,18 +12792,18 @@ WHERE NOT EXISTS ( AssertSql( """ -SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] -FROM [Squads] AS [s] +SELECT `s`.`Id`, `s`.`Banner`, `s`.`Banner5`, `s`.`InternalNumber`, `s`.`Name` +FROM `Squads` AS `s` WHERE NOT EXISTS ( SELECT 1 FROM ( - SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank], N'Gear' AS [Discriminator] - FROM [Gears] AS [g] + 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], N'Officer' AS [Discriminator] - FROM [Officers] AS [o] - ) AS [t] - WHERE [s].[Id] = [t].[SquadId] AND [t].[FullName] = N'Anthony Carmine') + 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 `s`.`Id` = `t`.`SquadId` AND `t`.`FullName` = 'Anthony Carmine') """); } @@ -12834,18 +12834,18 @@ WHERE NOT EXISTS ( AssertSql( """ -SELECT [t].[Nickname], [t].[SquadId], [t].[AssignedCityName], [t].[CityOfBirthName], [t].[FullName], [t].[HasSoulPatch], [t].[LeaderNickname], [t].[LeaderSquadId], [t].[Rank], [t].[Discriminator] +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], N'Gear' AS [Discriminator] - FROM [Gears] AS [g] + 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], N'Officer' AS [Discriminator] - FROM [Officers] AS [o] -) AS [t] + 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 EXISTS ( SELECT 1 - FROM [Weapons] AS [w] - WHERE [t].[FullName] = [w].[OwnerFullName] AND [w].[Name] = N'Hammer of Dawn') + FROM `Weapons` AS `w` + WHERE `t`.`FullName` = `w`.`OwnerFullName` AND `w`.`Name` = 'Hammer of Dawn') """); } diff --git a/test/EFCore.Jet.FunctionalTests/Query/TPTGearsOfWarQueryJetTest.cs b/test/EFCore.Jet.FunctionalTests/Query/TPTGearsOfWarQueryJetTest.cs index bdd33fc..a95c4da 100644 --- a/test/EFCore.Jet.FunctionalTests/Query/TPTGearsOfWarQueryJetTest.cs +++ b/test/EFCore.Jet.FunctionalTests/Query/TPTGearsOfWarQueryJetTest.cs @@ -10403,13 +10403,13 @@ WHERE NOT EXISTS ( AssertSql( """ -SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] -FROM [Squads] AS [s] +SELECT `s`.`Id`, `s`.`Banner`, `s`.`Banner5`, `s`.`InternalNumber`, `s`.`Name` +FROM `Squads` AS `s` WHERE NOT EXISTS ( SELECT 1 - FROM [Gears] AS [g] - LEFT JOIN [Officers] AS [o] ON [g].[Nickname] = [o].[Nickname] AND [g].[SquadId] = [o].[SquadId] - WHERE [s].[Id] = [g].[SquadId] AND [g].[FullName] = N'Anthony Carmine') + FROM `Gears` AS `g` + LEFT JOIN `Officers` AS `o` ON `g`.`Nickname` = `o`.`Nickname` AND `g`.`SquadId` = `o`.`SquadId` + WHERE `s`.`Id` = `g`.`SquadId` AND `g`.`FullName` = 'Anthony Carmine') """); } @@ -10435,15 +10435,13 @@ WHERE NOT EXISTS ( AssertSql( """ -SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank], CASE - WHEN [o].[Nickname] IS NOT NULL THEN N'Officer' -END AS [Discriminator] -FROM [Gears] AS [g] -LEFT JOIN [Officers] AS [o] ON [g].[Nickname] = [o].[Nickname] AND [g].[SquadId] = [o].[SquadId] +SELECT `g`.`Nickname`, `g`.`SquadId`, `g`.`AssignedCityName`, `g`.`CityOfBirthName`, `g`.`FullName`, `g`.`HasSoulPatch`, `g`.`LeaderNickname`, `g`.`LeaderSquadId`, `g`.`Rank`, IIF(`o`.`Nickname` IS NOT NULL, 'Officer', NULL) AS `Discriminator` +FROM `Gears` AS `g` +LEFT JOIN `Officers` AS `o` ON `g`.`Nickname` = `o`.`Nickname` AND `g`.`SquadId` = `o`.`SquadId` WHERE NOT EXISTS ( SELECT 1 - FROM [Weapons] AS [w] - WHERE [g].[FullName] = [w].[OwnerFullName] AND [w].[Name] = N'Hammer of Dawn') + FROM `Weapons` AS `w` + WHERE `g`.`FullName` = `w`.`OwnerFullName` AND `w`.`Name` = 'Hammer of Dawn') """); }