diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs index 8a6da7c2581..ea56be5271f 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs @@ -1059,6 +1059,7 @@ void IPrintableExpression.Print(ExpressionPrinter expressionPrinter) expressionPrinter.Visit(ServerQueryExpression); } + expressionPrinter.AppendLine(); expressionPrinter.AppendLine("ProjectionMapping:"); using (expressionPrinter.Indent()) { diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs index 97027a2f40a..3543db62faf 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs @@ -120,18 +120,26 @@ protected override ShapedQueryExpression TranslateAll(ShapedQueryExpression sour Check.NotNull(source, nameof(source)); Check.NotNull(predicate, nameof(predicate)); - var inMemoryQueryExpression = (InMemoryQueryExpression)source.QueryExpression; - predicate = TranslateLambdaExpression(source, predicate, preserveType: true); - if (predicate == null) + predicate = Expression.Lambda(Expression.Not(predicate.Body), predicate.Parameters); + source = TranslateWhere(source, predicate); + if (source == null) { return null; } + var inMemoryQueryExpression = (InMemoryQueryExpression)source.QueryExpression; + + if (source.ShaperExpression is GroupByShaperExpression) + { + inMemoryQueryExpression.ReplaceProjectionMapping(new Dictionary()); + inMemoryQueryExpression.PushdownIntoSubquery(); + } + inMemoryQueryExpression.UpdateServerQueryExpression( - Expression.Call( - EnumerableMethods.All.MakeGenericMethod(inMemoryQueryExpression.CurrentParameter.Type), - inMemoryQueryExpression.ServerQueryExpression, - predicate)); + Expression.Not( + Expression.Call( + EnumerableMethods.AnyWithoutPredicate.MakeGenericMethod(inMemoryQueryExpression.CurrentParameter.Type), + inMemoryQueryExpression.ServerQueryExpression))); return source.UpdateShaperExpression(inMemoryQueryExpression.GetSingleScalarProjection()); } @@ -144,30 +152,28 @@ protected override ShapedQueryExpression TranslateAll(ShapedQueryExpression sour /// protected override ShapedQueryExpression TranslateAny(ShapedQueryExpression source, LambdaExpression predicate) { - var inMemoryQueryExpression = (InMemoryQueryExpression)source.QueryExpression; - - if (predicate == null) - { - inMemoryQueryExpression.UpdateServerQueryExpression( - Expression.Call( - EnumerableMethods.AnyWithoutPredicate.MakeGenericMethod(inMemoryQueryExpression.CurrentParameter.Type), - inMemoryQueryExpression.ServerQueryExpression)); - } - else + if (predicate != null) { - predicate = TranslateLambdaExpression(source, predicate, preserveType: true); - if (predicate == null) + source = TranslateWhere(source, predicate); + if (source == null) { return null; } + } - inMemoryQueryExpression.UpdateServerQueryExpression( - Expression.Call( - EnumerableMethods.AnyWithPredicate.MakeGenericMethod(inMemoryQueryExpression.CurrentParameter.Type), - inMemoryQueryExpression.ServerQueryExpression, - predicate)); + var inMemoryQueryExpression = (InMemoryQueryExpression)source.QueryExpression; + + if (source.ShaperExpression is GroupByShaperExpression) + { + inMemoryQueryExpression.ReplaceProjectionMapping(new Dictionary()); + inMemoryQueryExpression.PushdownIntoSubquery(); } + inMemoryQueryExpression.UpdateServerQueryExpression( + Expression.Call( + EnumerableMethods.AnyWithoutPredicate.MakeGenericMethod(inMemoryQueryExpression.CurrentParameter.Type), + inMemoryQueryExpression.ServerQueryExpression)); + return source.UpdateShaperExpression(inMemoryQueryExpression.GetSingleScalarProjection()); } @@ -256,30 +262,28 @@ protected override ShapedQueryExpression TranslateCount(ShapedQueryExpression so { Check.NotNull(source, nameof(source)); - var inMemoryQueryExpression = (InMemoryQueryExpression)source.QueryExpression; - - if (predicate == null) - { - inMemoryQueryExpression.UpdateServerQueryExpression( - Expression.Call( - EnumerableMethods.CountWithoutPredicate.MakeGenericMethod(inMemoryQueryExpression.CurrentParameter.Type), - inMemoryQueryExpression.ServerQueryExpression)); - } - else + if (predicate != null) { - predicate = TranslateLambdaExpression(source, predicate, preserveType: true); - if (predicate == null) + source = TranslateWhere(source, predicate); + if (source == null) { return null; } + } - inMemoryQueryExpression.UpdateServerQueryExpression( - Expression.Call( - EnumerableMethods.CountWithPredicate.MakeGenericMethod(inMemoryQueryExpression.CurrentParameter.Type), - inMemoryQueryExpression.ServerQueryExpression, - predicate)); + var inMemoryQueryExpression = (InMemoryQueryExpression)source.QueryExpression; + + if (source.ShaperExpression is GroupByShaperExpression) + { + inMemoryQueryExpression.ReplaceProjectionMapping(new Dictionary()); + inMemoryQueryExpression.PushdownIntoSubquery(); } + inMemoryQueryExpression.UpdateServerQueryExpression( + Expression.Call( + EnumerableMethods.CountWithoutPredicate.MakeGenericMethod(inMemoryQueryExpression.CurrentParameter.Type), + inMemoryQueryExpression.ServerQueryExpression)); + return source.UpdateShaperExpression(inMemoryQueryExpression.GetSingleScalarProjection()); } @@ -720,37 +724,33 @@ protected override ShapedQueryExpression TranslateLeftJoin( /// 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. /// - protected override ShapedQueryExpression TranslateLongCount( - ShapedQueryExpression source, LambdaExpression predicate) + protected override ShapedQueryExpression TranslateLongCount(ShapedQueryExpression source, LambdaExpression predicate) { Check.NotNull(source, nameof(source)); - var inMemoryQueryExpression = (InMemoryQueryExpression)source.QueryExpression; - - if (predicate == null) - { - inMemoryQueryExpression.UpdateServerQueryExpression( - Expression.Call( - EnumerableMethods.LongCountWithoutPredicate.MakeGenericMethod( - inMemoryQueryExpression.CurrentParameter.Type), - inMemoryQueryExpression.ServerQueryExpression)); - } - else + if (predicate != null) { - predicate = TranslateLambdaExpression(source, predicate, preserveType: true); - if (predicate == null) + source = TranslateWhere(source, predicate); + if (source == null) { return null; } + } - inMemoryQueryExpression.UpdateServerQueryExpression( - Expression.Call( - EnumerableMethods.LongCountWithPredicate.MakeGenericMethod( - inMemoryQueryExpression.CurrentParameter.Type), - inMemoryQueryExpression.ServerQueryExpression, - predicate)); + var inMemoryQueryExpression = (InMemoryQueryExpression)source.QueryExpression; + + if (source.ShaperExpression is GroupByShaperExpression) + { + inMemoryQueryExpression.ReplaceProjectionMapping(new Dictionary()); + inMemoryQueryExpression.PushdownIntoSubquery(); } + inMemoryQueryExpression.UpdateServerQueryExpression( + Expression.Call( + EnumerableMethods.LongCountWithoutPredicate.MakeGenericMethod( + inMemoryQueryExpression.CurrentParameter.Type), + inMemoryQueryExpression.ServerQueryExpression)); + return source.UpdateShaperExpression(inMemoryQueryExpression.GetSingleScalarProjection()); } diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index ecef7ccd412..5a64572dd11 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -235,6 +235,7 @@ protected override ShapedQueryExpression TranslateAverage(ShapedQueryExpression var selectExpression = (SelectExpression)source.QueryExpression; selectExpression.PrepareForAggregate(); + HandleGroupByForAggregate(selectExpression); var newSelector = selector == null || selector.Body == selector.Parameters[0] @@ -318,6 +319,8 @@ protected override ShapedQueryExpression TranslateCount(ShapedQueryExpression so } } + HandleGroupByForAggregate(selectExpression, eraseProjection: true); + var translation = _sqlTranslator.TranslateCount(); if (translation == null) { @@ -668,6 +671,8 @@ protected override ShapedQueryExpression TranslateLongCount(ShapedQueryExpressio } } + HandleGroupByForAggregate(selectExpression, eraseProjection: true); + var translation = _sqlTranslator.TranslateLongCount(); if (translation == null) { @@ -688,6 +693,7 @@ protected override ShapedQueryExpression TranslateMax(ShapedQueryExpression sour var selectExpression = (SelectExpression)source.QueryExpression; selectExpression.PrepareForAggregate(); + HandleGroupByForAggregate(selectExpression); var newSelector = selector == null || selector.Body == selector.Parameters[0] @@ -713,6 +719,7 @@ protected override ShapedQueryExpression TranslateMin(ShapedQueryExpression sour var selectExpression = (SelectExpression)source.QueryExpression; selectExpression.PrepareForAggregate(); + HandleGroupByForAggregate(selectExpression); var newSelector = selector == null || selector.Body == selector.Parameters[0] @@ -1048,6 +1055,7 @@ protected override ShapedQueryExpression TranslateSum(ShapedQueryExpression sour var selectExpression = (SelectExpression)source.QueryExpression; selectExpression.PrepareForAggregate(); + HandleGroupByForAggregate(selectExpression); var newSelector = selector == null || selector.Body == selector.Parameters[0] @@ -1479,6 +1487,24 @@ private static Expression AccessField( string fieldName) => Expression.Field(targetExpression, transparentIdentifierType.GetTypeInfo().GetDeclaredField(fieldName)); + private static void HandleGroupByForAggregate(SelectExpression selectExpression, bool eraseProjection = false) + { + if (selectExpression.GroupBy.Count > 0) + { + if (eraseProjection) + { + selectExpression.ReplaceProjectionMapping(new Dictionary()); + selectExpression.AddToProjection(selectExpression.GroupBy[0]); + selectExpression.PushdownIntoSubquery(); + selectExpression.ClearProjection(); + } + else + { + selectExpression.PushdownIntoSubquery(); + } + } + } + private ShapedQueryExpression AggregateResultShaper( ShapedQueryExpression source, Expression projection, bool throwWhenEmpty, Type resultType) { diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs index 26698b2c28c..e8066a6e0e3 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs @@ -462,8 +462,7 @@ public void PrepareForAggregate() { if (IsDistinct || Limit != null - || Offset != null - || GroupBy.Count > 0) + || Offset != null) { PushdownIntoSubquery(); } @@ -1652,7 +1651,7 @@ private SqlBinaryExpression ValidateKeyComparison( bool allowNonEquality) { if (sqlBinaryExpression.OperatorType == ExpressionType.Equal - || (allowNonEquality && + || (allowNonEquality && (sqlBinaryExpression.OperatorType == ExpressionType.NotEqual || sqlBinaryExpression.OperatorType == ExpressionType.GreaterThan || sqlBinaryExpression.OperatorType == ExpressionType.GreaterThanOrEqual diff --git a/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs index 10778a7bcaf..10ede25a272 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs @@ -2518,7 +2518,7 @@ public virtual Task Any_after_GroupBy_aggregate(bool async) ss => ss.Set().GroupBy(o => o.CustomerID).Select(g => g.Sum(gg => gg.OrderID))); } - [ConditionalTheory(Skip = "Issue#15097")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Count_after_GroupBy_without_aggregate(bool async) { @@ -2527,7 +2527,17 @@ public virtual Task Count_after_GroupBy_without_aggregate(bool async) ss => ss.Set().GroupBy(o => o.CustomerID)); } - [ConditionalTheory(Skip = "Issue#15097")] + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Count_with_predicate_after_GroupBy_without_aggregate(bool async) + { + return AssertCount( + async, + ss => ss.Set().GroupBy(o => o.CustomerID), + g => g.Count() > 1); + } + + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task LongCount_after_GroupBy_without_aggregate(bool async) { @@ -2536,6 +2546,45 @@ public virtual Task LongCount_after_GroupBy_without_aggregate(bool async) ss => ss.Set().GroupBy(o => o.CustomerID)); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task LongCount_with_predicate_after_GroupBy_without_aggregate(bool async) + { + return AssertLongCount( + async, + ss => ss.Set().GroupBy(o => o.CustomerID), + g => g.Count() > 1); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Any_after_GroupBy_without_aggregate(bool async) + { + return AssertAny( + async, + ss => ss.Set().GroupBy(o => o.CustomerID)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Any_with_predicate_after_GroupBy_without_aggregate(bool async) + { + return AssertAny( + async, + ss => ss.Set().GroupBy(o => o.CustomerID), + g => g.Count() > 1); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task All_with_predicate_after_GroupBy_without_aggregate(bool async) + { + return AssertAll( + async, + ss => ss.Set().GroupBy(o => o.CustomerID), + g => g.Count() > 1); + } + #endregion # region GroupByInSubquery @@ -2657,8 +2706,6 @@ public virtual Task GroupBy_scalar_subquery(bool async) elementSorter: e => e.Key); } - - [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task GroupBy_scalar_aggregate_in_set_operation(bool async) diff --git a/test/EFCore.Specification.Tests/Query/QueryTestBase.cs b/test/EFCore.Specification.Tests/Query/QueryTestBase.cs index f3c7991df89..a6e807ee0c7 100644 --- a/test/EFCore.Specification.Tests/Query/QueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/QueryTestBase.cs @@ -398,12 +398,27 @@ protected Task AssertLongCount( Func> query) => AssertLongCount(async, query, query); + protected Task AssertLongCount( + bool async, + Func> query, + Expression> predicate) + => AssertLongCount(async, query, query, predicate, predicate); + protected Task AssertLongCount( bool async, Func> actualQuery, Func> expectedQuery) => QueryAsserter.AssertLongCount(actualQuery, expectedQuery, async); + protected Task AssertLongCount( + bool async, + Func> actualQuery, + Func> expectedQuery, + Expression> actualPredicate, + Expression> expectedPredicate) + => QueryAsserter.AssertLongCount( + actualQuery, expectedQuery, actualPredicate, expectedPredicate, async); + protected Task AssertMin( bool async, Func> query, diff --git a/test/EFCore.Specification.Tests/TestUtilities/QueryAsserter.cs b/test/EFCore.Specification.Tests/TestUtilities/QueryAsserter.cs index 52660ffe76f..88df3422199 100644 --- a/test/EFCore.Specification.Tests/TestUtilities/QueryAsserter.cs +++ b/test/EFCore.Specification.Tests/TestUtilities/QueryAsserter.cs @@ -237,28 +237,15 @@ public async Task AssertAny( Expression> expectedPredicate, bool async = false) { - using (var context = _contextCreator()) - { - var actual = async - ? await RewriteServerQuery(actualQuery(SetSourceCreator(context))).AnyAsync(actualPredicate) - : RewriteServerQuery(actualQuery(SetSourceCreator(context))).Any(actualPredicate); - - var rewrittenExpectedPredicate = (Expression>)new ExpectedQueryRewritingVisitor().Visit(expectedPredicate); - var expected = RewriteExpectedQuery(expectedQuery(ExpectedData)).Any(rewrittenExpectedPredicate); - - Assert.Equal(expected, actual); - } - - using (var context = _contextCreator()) - { - var actual = async - ? await RewriteServerQuery(actualQuery(SetSourceCreator(context))).AnyAsync() - : RewriteServerQuery(actualQuery(SetSourceCreator(context))).Any(); + using var context = _contextCreator(); + var actual = async + ? await RewriteServerQuery(actualQuery(SetSourceCreator(context))).AnyAsync(actualPredicate) + : RewriteServerQuery(actualQuery(SetSourceCreator(context))).Any(actualPredicate); - var expected = expectedQuery(ExpectedData).Any(); + var rewrittenExpectedPredicate = (Expression>)new ExpectedQueryRewritingVisitor().Visit(expectedPredicate); + var expected = RewriteExpectedQuery(expectedQuery(ExpectedData)).Any(rewrittenExpectedPredicate); - Assert.Equal(expected, actual); - } + Assert.Equal(expected, actual); } public async Task AssertAll( diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs index 9a0aa9d8e41..5fb1ad6505b 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs @@ -1662,7 +1662,7 @@ public override async Task Count_after_GroupBy_aggregate(bool async) AssertSql( @"SELECT COUNT(*) FROM ( - SELECT COALESCE(SUM([o].[OrderID]), 0) AS [c] + SELECT [o].[CustomerID] FROM [Orders] AS [o] GROUP BY [o].[CustomerID] ) AS [t]"); @@ -1746,14 +1746,98 @@ public override async Task Count_after_GroupBy_without_aggregate(bool async) { await base.Count_after_GroupBy_without_aggregate(async); - AssertSql(" "); + AssertSql( + @"SELECT COUNT(*) +FROM ( + SELECT [o].[CustomerID] + FROM [Orders] AS [o] + GROUP BY [o].[CustomerID] +) AS [t]"); + } + + public override async Task Count_with_predicate_after_GroupBy_without_aggregate(bool async) + { + await base.Count_with_predicate_after_GroupBy_without_aggregate(async); + + AssertSql( + @"SELECT COUNT(*) +FROM ( + SELECT [o].[CustomerID] + FROM [Orders] AS [o] + GROUP BY [o].[CustomerID] + HAVING COUNT(*) > 1 +) AS [t]"); } public override async Task LongCount_after_GroupBy_without_aggregate(bool async) { await base.LongCount_after_GroupBy_without_aggregate(async); - AssertSql(" "); + AssertSql( + @"SELECT COUNT_BIG(*) +FROM ( + SELECT [o].[CustomerID] + FROM [Orders] AS [o] + GROUP BY [o].[CustomerID] +) AS [t]"); + } + + public override async Task LongCount_with_predicate_after_GroupBy_without_aggregate(bool async) + { + await base.LongCount_with_predicate_after_GroupBy_without_aggregate(async); + + AssertSql( + @"SELECT COUNT_BIG(*) +FROM ( + SELECT [o].[CustomerID] + FROM [Orders] AS [o] + GROUP BY [o].[CustomerID] + HAVING COUNT(*) > 1 +) AS [t]"); + } + + public override async Task Any_after_GroupBy_without_aggregate(bool async) + { + await base.Any_after_GroupBy_without_aggregate(async); + + AssertSql( + @"SELECT CASE + WHEN EXISTS ( + SELECT 1 + FROM [Orders] AS [o] + GROUP BY [o].[CustomerID]) THEN CAST(1 AS bit) + ELSE CAST(0 AS bit) +END"); + } + + public override async Task Any_with_predicate_after_GroupBy_without_aggregate(bool async) + { + await base.Any_with_predicate_after_GroupBy_without_aggregate(async); + + AssertSql( + @"SELECT CASE + WHEN EXISTS ( + SELECT 1 + FROM [Orders] AS [o] + GROUP BY [o].[CustomerID] + HAVING COUNT(*) > 1) THEN CAST(1 AS bit) + ELSE CAST(0 AS bit) +END"); + } + + public override async Task All_with_predicate_after_GroupBy_without_aggregate(bool async) + { + await base.All_with_predicate_after_GroupBy_without_aggregate(async); + + AssertSql( + @"SELECT CASE + WHEN NOT EXISTS ( + SELECT 1 + FROM [Orders] AS [o] + GROUP BY [o].[CustomerID] + HAVING COUNT(*) <= 1) THEN CAST(1 AS bit) + ELSE CAST(0 AS bit) +END"); } public override async Task GroupBy_based_on_renamed_property_simple(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs index a2c1dd34a17..ebc9f621a0d 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs @@ -1364,13 +1364,6 @@ SELECT 1 FROM [Customers] AS [c] WHERE [c].[ContactName] IS NOT NULL AND ([c].[ContactName] LIKE N'A%')) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) -END", - // - @"SELECT CASE - WHEN EXISTS ( - SELECT 1 - FROM [Customers] AS [c]) THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) END"); } @@ -2099,18 +2092,6 @@ OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY ) AS [t] WHERE [t].[CustomerID] LIKE N'C%') THEN CAST(1 AS bit) ELSE CAST(0 AS bit) -END", - // - @"@__p_0='5' -@__p_1='7' - -SELECT CASE - WHEN EXISTS ( - SELECT 1 - FROM [Customers] AS [c] - ORDER BY [c].[CustomerID] - OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY) THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) END"); } @@ -2131,16 +2112,6 @@ ORDER BY [c].[CustomerID] ) AS [t] WHERE [t].[CustomerID] LIKE N'B%') THEN CAST(1 AS bit) ELSE CAST(0 AS bit) -END", - // - @"@__p_0='5' - -SELECT CASE - WHEN EXISTS ( - SELECT TOP(@__p_0) 1 - FROM [Customers] AS [c] - ORDER BY [c].[CustomerID]) THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) END"); }