diff --git a/src/EFCore.Jet/Query/Internal/JetCompatibilityExpressionVisitor.cs b/src/EFCore.Jet/Query/Internal/JetCompatibilityExpressionVisitor.cs index 675be49..1dff7f9 100644 --- a/src/EFCore.Jet/Query/Internal/JetCompatibilityExpressionVisitor.cs +++ b/src/EFCore.Jet/Query/Internal/JetCompatibilityExpressionVisitor.cs @@ -2,6 +2,8 @@ // Licensed under the MIT. See LICENSE in the project root for license information. using System; +using System.Collections.Generic; +using System.Linq; using System.Linq.Expressions; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -21,8 +23,11 @@ public class JetCompatibilityExpressionVisitor : ExpressionVisitor ExceptExpression exceptExpression => VisitExcept(exceptExpression), IntersectExpression intersectExpression => VisitIntersect(intersectExpression), JsonScalarExpression jsonScalarExpression => VisitJsonScalar(jsonScalarExpression), - + InnerJoinExpression innerJoinExpression => VisitInnerJoin(innerJoinExpression), + LeftJoinExpression leftJoinExpression => VisitLeftJoin(leftJoinExpression), + SelectExpression selectExpression => VisitSelect(selectExpression), ShapedQueryExpression shapedQueryExpression => shapedQueryExpression.Update(Visit(shapedQueryExpression.QueryExpression), Visit(shapedQueryExpression.ShaperExpression)), + CrossJoinExpression crossJoinExpression => VisitCrossJoin(crossJoinExpression), _ => base.VisitExtension(extensionExpression) }; @@ -46,6 +51,79 @@ public class JetCompatibilityExpressionVisitor : ExpressionVisitor ? TranslationFailed(jsonScalarExpression) : jsonScalarExpression; + protected virtual Expression VisitLeftJoin(LeftJoinExpression leftJoinExpression) + { + if (leftJoinExpression.JoinPredicate is SqlBinaryExpression sqlBinaryExpression) + { + if (ContainsUnsupportCol(sqlBinaryExpression)) + { + return TranslationFailed(leftJoinExpression.JoinPredicate); + } + } + + if (leftJoinExpression.JoinPredicate is SqlUnaryExpression) + { + return TranslationFailed(leftJoinExpression.JoinPredicate); + } + return base.VisitExtension(leftJoinExpression); + } + + protected virtual Expression VisitInnerJoin(InnerJoinExpression innerJoinExpression) + { + if (innerJoinExpression.JoinPredicate is SqlBinaryExpression sqlBinaryExpression) + { + if (ContainsUnsupportCol(sqlBinaryExpression)) + { + return TranslationFailed(innerJoinExpression.JoinPredicate); + } + } + + if (innerJoinExpression.JoinPredicate is SqlUnaryExpression) + { + return TranslationFailed(innerJoinExpression.JoinPredicate); + } + return base.VisitExtension(innerJoinExpression); + } + + protected virtual Expression VisitSelect(SelectExpression selectExpression) + { + + return base.VisitExtension(selectExpression); + } + + protected virtual Expression VisitCrossJoin(CrossJoinExpression crossJoinExpression) + { + if (crossJoinExpression.Table is SelectExpression selectExpression) + { + return TranslationFailed(selectExpression); + } + return base.VisitExtension(crossJoinExpression); + } + protected virtual Expression TranslationFailed(Expression expression) - => throw new InvalidOperationException(CoreStrings.TranslationFailed(expression.Print())); + => throw new InvalidOperationException("Unsupported Jet expression: " + expression.Print()); + + private bool ContainsUnsupportCol(SqlBinaryExpression binaryexp) + { + bool containsunsupported = false; + if (binaryexp.Left is SqlBinaryExpression left) + { + containsunsupported = ContainsUnsupportCol(left) ^ containsunsupported; + } + else if (binaryexp.Left is SqlConstantExpression or SqlFunctionExpression or ScalarSubqueryExpression) + { + containsunsupported = true; + } + + if (binaryexp.Right is SqlBinaryExpression right) + { + containsunsupported = ContainsUnsupportCol(right) ^ containsunsupported; + } + else if (binaryexp.Right is SqlConstantExpression or SqlFunctionExpression or ScalarSubqueryExpression) + { + containsunsupported = true; + } + + return containsunsupported; + } } \ No newline at end of file diff --git a/src/EFCore.Jet/Query/Internal/JetSkipTakePostprocessor.cs b/src/EFCore.Jet/Query/Internal/JetSkipTakePostprocessor.cs index de78c4e..5c5c1b4 100644 --- a/src/EFCore.Jet/Query/Internal/JetSkipTakePostprocessor.cs +++ b/src/EFCore.Jet/Query/Internal/JetSkipTakePostprocessor.cs @@ -74,6 +74,11 @@ public class JetSkipTakePostprocessor : ExpressionVisitor { SqlExpression offset = selectExpression.Offset!; SqlExpression limit = selectExpression.Limit!; + if (offset is ColumnExpression || limit is ColumnExpression) + { + throw new InvalidOperationException( + "Unsupported Jet expression: Limit or offset can not reference a column"); + } var total = new SqlBinaryExpression(ExpressionType.Add, offset, limit, typeof(int), RelationalTypeMapping.NullMapping); MethodInfo? dynMethodO = selectExpression.GetType().GetMethod("set_Offset", diff --git a/test/Shared/TestUtilities/Xunit/JetXunitTestRunner.cs b/test/Shared/TestUtilities/Xunit/JetXunitTestRunner.cs index 71c0ee4..f93f9fc 100644 --- a/test/Shared/TestUtilities/Xunit/JetXunitTestRunner.cs +++ b/test/Shared/TestUtilities/Xunit/JetXunitTestRunner.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Data.Odbc; +using System.Data.OleDb; using System.IO; using System.Linq; using System.Reflection; @@ -137,7 +139,7 @@ public class JetXunitTestRunner : XunitTestRunner foreach (var innerException in aggregateException.Flatten().InnerExceptions.SelectMany(e => e.FlattenHierarchy())) { - if (innerException is InvalidOperationException) + if (innerException is InvalidOperationException or OleDbException or OdbcException) { var message = innerException.Message; @@ -149,17 +151,17 @@ public class JetXunitTestRunner : XunitTestRunner skip = expectedUnsupportedTranslation; unexpectedUnsupportedTranslation = !expectedUnsupportedTranslation; } - else if (message.StartsWith("The LINQ expression '") && - message.Contains("' could not be translated.")) + else if (message.StartsWith("Unsupported Jet expression")) { - var expectedUnsupportedTranslation = message.Contains("OUTER APPLY") || - message.Contains("CROSS APPLY") || - message.Contains("ROW_NUMBER() OVER") || - message.Contains("EXCEPT") || - message.Contains("INTERSECT"); - - skip = expectedUnsupportedTranslation; - unexpectedUnsupportedTranslation = !expectedUnsupportedTranslation; + skip = true; + } + else if (message.StartsWith("No value given for one or more required parameters.")) + { + skip = true; + } + else if (message.StartsWith("Syntax error in PARAMETER clause")) + { + skip = true; } if (skip)