diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosQuerySqlGenerator.cs b/src/EFCore.Cosmos/Query/Internal/CosmosQuerySqlGenerator.cs index a0e4c7207b8..d55224c3f8e 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosQuerySqlGenerator.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosQuerySqlGenerator.cs @@ -476,8 +476,8 @@ protected override Expression VisitSqlBinary(SqlBinaryExpression sqlBinaryExpres // Arithmetic ExpressionType.Add => " + ", ExpressionType.Subtract => " - ", - ExpressionType.Multiply => " * " , - ExpressionType.Divide => " / " , + ExpressionType.Multiply => " * ", + ExpressionType.Divide => " / ", ExpressionType.Modulo => " % ", // Bitwise >>> (zero-fill right shift) not available in C# @@ -513,6 +513,11 @@ protected override Expression VisitSqlBinary(SqlBinaryExpression sqlBinaryExpres { op = " || "; } + else if (sqlBinaryExpression.OperatorType == ExpressionType.ExclusiveOr + && sqlBinaryExpression.Type == typeof(bool)) + { + op = " != "; + } _sqlBuilder.Append(op); diff --git a/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs b/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs index 9c5e3be185f..d3b6b7035bd 100644 --- a/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs +++ b/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs @@ -152,6 +152,7 @@ private SqlExpression ApplyTypeMappingOnSqlBinary( case ExpressionType.RightShift: case ExpressionType.And: case ExpressionType.Or: + case ExpressionType.ExclusiveOr: case ExpressionType.Coalesce: { inferredTypeMapping = typeMapping ?? ExpressionExtensions.InferTypeMapping(left, right); diff --git a/src/EFCore.Relational/Query/QuerySqlGenerator.cs b/src/EFCore.Relational/Query/QuerySqlGenerator.cs index 5ba1ee7fd78..36a605bdab2 100644 --- a/src/EFCore.Relational/Query/QuerySqlGenerator.cs +++ b/src/EFCore.Relational/Query/QuerySqlGenerator.cs @@ -1096,6 +1096,7 @@ protected virtual string GetOperator(SqlBinaryExpression binaryExpression) ExpressionType.Modulo => " % ", ExpressionType.And => " & ", ExpressionType.Or => " | ", + ExpressionType.ExclusiveOr => " ^ ", _ => throw new UnreachableException($"Unsupported unary OperatorType: {binaryExpression.OperatorType}") }; diff --git a/src/EFCore.Relational/Query/SqlExpressionFactory.cs b/src/EFCore.Relational/Query/SqlExpressionFactory.cs index 0a7ae6228eb..a9061071597 100644 --- a/src/EFCore.Relational/Query/SqlExpressionFactory.cs +++ b/src/EFCore.Relational/Query/SqlExpressionFactory.cs @@ -256,6 +256,7 @@ private SqlExpression ApplyTypeMappingOnSqlBinary( case ExpressionType.Modulo: case ExpressionType.And: case ExpressionType.Or: + case ExpressionType.ExclusiveOr: { inferredTypeMapping = typeMapping ?? ExpressionExtensions.InferTypeMapping(left, right); resultType = inferredTypeMapping?.ClrType ?? left.Type; diff --git a/src/EFCore.Relational/Query/SqlExpressions/SqlBinaryExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SqlBinaryExpression.cs index 0b84052639d..a60b5841ec9 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SqlBinaryExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SqlBinaryExpression.cs @@ -100,6 +100,7 @@ internal static bool IsValidOperator(ExpressionType operatorType) case ExpressionType.Equal: case ExpressionType.NotEqual: case ExpressionType.Coalesce: + case ExpressionType.ExclusiveOr: return true; default: return false; diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs index 6894a9bc450..717d31dad4f 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs @@ -653,6 +653,7 @@ protected override bool TryGetOperatorInfo(SqlExpression expression, out int pre ExpressionType.Subtract => (700, false), ExpressionType.And => (700, true), ExpressionType.Or => (700, true), + ExpressionType.ExclusiveOr => (700, true), ExpressionType.LeftShift => (700, true), ExpressionType.RightShift => (700, true), ExpressionType.LessThan => (500, false), diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs index 0c3278ae690..62b3a4db5f8 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs @@ -211,6 +211,11 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) if (visitedExpression is SqlBinaryExpression sqlBinary) { + if (sqlBinary.OperatorType == ExpressionType.ExclusiveOr) + { + return QueryCompilationContext.NotTranslatedExpression; + } + if (sqlBinary.OperatorType == ExpressionType.Modulo && (ModuloFunctions.TryGetValue(GetProviderType(sqlBinary.Left), out var function) || ModuloFunctions.TryGetValue(GetProviderType(sqlBinary.Right), out function))) diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs index f72eea8d982..41e1d99ceb1 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs @@ -2369,6 +2369,20 @@ public override Task Where_bitwise_binary_or(bool async) SELECT c FROM root c WHERE ((c["Discriminator"] = "Order") AND ((c["OrderID"] | 10248) = 10248)) +"""); + }); + + public override Task Where_bitwise_binary_xor(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Where_bitwise_binary_xor(async); + + AssertSql( + """ +SELECT c +FROM root c +WHERE ((c["Discriminator"] = "Order") AND ((c["OrderID"] ^ 1) = 10249)) """); }); diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs index 9d7781da38b..f9db421c40e 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs @@ -150,15 +150,21 @@ FROM root c """); }); - public override async Task Where_bitwise_xor(bool async) - { - // Bitwise operators on booleans. Issue #13168. - Assert.Equal( - CosmosStrings.UnsupportedOperatorForSqlExpression("ExclusiveOr", "SqlBinaryExpression"), - (await Assert.ThrowsAsync(() => base.Where_bitwise_xor(async))).Message); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public override Task Where_bitwise_xor(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Where_bitwise_xor(a); - AssertSql(); - } + AssertSql( + """ +SELECT c +FROM root c +WHERE ((c["Discriminator"] = "Customer") AND ((c["CustomerID"] = "ALFKI") != true)) +"""); + }); [ConditionalTheory] [MemberData(nameof(IsAsyncData))] diff --git a/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs index 3592a8f744b..dee6147bff4 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs @@ -3265,6 +3265,16 @@ public virtual Task Where_bitwise_binary_or(bool async) async, ss => ss.Set().Where(o => (o.OrderID | 10248) == 10248)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Where_bitwise_binary_xor(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Where(o => (o.OrderID ^ 1) == 10249)); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Select_bitwise_or_with_logical_or(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs index 2893d0b69a6..ca7002f78cd 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs @@ -3257,6 +3257,18 @@ FROM [Orders] AS [o] """); } + public override async Task Where_bitwise_binary_xor(bool async) + { + await base.Where_bitwise_binary_xor(async); + + AssertSql( + """ +SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] +FROM [Orders] AS [o] +WHERE [o].[OrderID] ^ 1 = 10249 +"""); + } + public override async Task Select_bitwise_or_with_logical_or(bool async) { await base.Select_bitwise_or_with_logical_or(async); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs index fcb86731186..67f7f8464cd 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs @@ -2446,14 +2446,14 @@ public override async Task Projection_when_arithmetic_mixed_subqueries(bool asyn """ @__p_0='3' -SELECT CAST([e0].[EmployeeID] AS bigint) + CAST([o0].[OrderID] AS bigint), [e0].[EmployeeID], [e0].[City], [e0].[Country], [e0].[FirstName], [e0].[ReportsTo], [e0].[Title], [o0].[OrderID], [o0].[CustomerID], [o0].[EmployeeID], [o0].[OrderDate], [o0].[OrderID] % 2 +SELECT CAST([e0].[EmployeeID] AS bigint) + CAST([o0].[OrderID] AS bigint) AS [Add], [e0].[Square], [e0].[EmployeeID], [e0].[City], [e0].[Country], [e0].[FirstName], [e0].[ReportsTo], [e0].[Title], 42 AS [Literal], [o0].[OrderID], [o0].[CustomerID], [o0].[EmployeeID], [o0].[OrderDate], [o0].[OrderID] % 2 AS [Mod] FROM ( SELECT TOP(@__p_0) [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] ORDER BY [o].[OrderID] ) AS [o0] CROSS JOIN ( - SELECT TOP(2) [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] + SELECT TOP(2) [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title], [e].[EmployeeID] ^ 2 AS [Square] FROM [Employees] AS [e] ORDER BY [e].[EmployeeID] ) AS [e0] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs index 1ba0c1ad5a3..b320cc70f0b 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs @@ -515,10 +515,17 @@ FROM [Customers] AS [c] public override async Task Where_bitwise_xor(bool async) { - // Cannot eval 'where (([c].CustomerID == \"ALFKI\") ^ True)'. Issue #16645. - await AssertTranslationFailed(() => base.Where_bitwise_xor(async)); + await base.Where_bitwise_xor(async); - AssertSql(); + AssertSql( + """ +SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] +FROM [Customers] AS [c] +WHERE CASE + WHEN [c].[CustomerID] = N'ALFKI' THEN CAST(1 AS bit) + ELSE CAST(0 AS bit) +END ^ CAST(1 AS bit) = CAST(1 AS bit) +"""); } public override async Task Where_simple_shadow(bool async) diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindMiscellaneousQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindMiscellaneousQuerySqliteTest.cs index c9351cae83a..0690d3b81de 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindMiscellaneousQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindMiscellaneousQuerySqliteTest.cs @@ -317,6 +317,11 @@ LIMIT @__p_0 """); } + [ConditionalTheory(Skip = "Issue #16645 bitwise xor support")] + [MemberData(nameof(IsAsyncData))] + public override Task Where_bitwise_binary_xor(bool async) + => AssertTranslationFailed(() => base.Where_bitwise_binary_xor(async)); + public override Task Complex_nested_query_doesnt_try_binding_to_grandparent_when_parent_returns_complex_result(bool async) => null;