@@ -161,7 +161,7 @@ protected virtual TableExpressionBase Visit(TableExpressionBase tableExpressionB
161
161
var newTable = Visit ( innerJoinExpression . Table ) ;
162
162
var newJoinPredicate = ProcessJoinPredicate ( innerJoinExpression . JoinPredicate ) ;
163
163
164
- return TryGetBoolConstantValue ( newJoinPredicate ) == true
164
+ return IsTrue ( newJoinPredicate )
165
165
? new CrossJoinExpression ( newTable )
166
166
: innerJoinExpression . Update ( newTable , newJoinPredicate ) ;
167
167
}
@@ -301,7 +301,7 @@ protected virtual SelectExpression Visit(SelectExpression selectExpression)
301
301
var predicate = Visit ( selectExpression . Predicate , allowOptimizedExpansion : true , out _ ) ;
302
302
changed |= predicate != selectExpression . Predicate ;
303
303
304
- if ( TryGetBoolConstantValue ( predicate ) == true )
304
+ if ( IsTrue ( predicate ) )
305
305
{
306
306
predicate = null ;
307
307
changed = true ;
@@ -333,7 +333,7 @@ protected virtual SelectExpression Visit(SelectExpression selectExpression)
333
333
var having = Visit ( selectExpression . Having , allowOptimizedExpansion : true , out _ ) ;
334
334
changed |= having != selectExpression . Having ;
335
335
336
- if ( TryGetBoolConstantValue ( having ) == true )
336
+ if ( IsTrue ( having ) )
337
337
{
338
338
having = null ;
339
339
changed = true ;
@@ -519,20 +519,17 @@ protected virtual SqlExpression VisitCase(CaseExpression caseExpression, bool al
519
519
var test = Visit (
520
520
whenClause . Test , allowOptimizedExpansion : testIsCondition , preserveColumnNullabilityInformation : true , out _ ) ;
521
521
522
- if ( TryGetBoolConstantValue ( test ) is bool testConstantBool )
522
+ if ( IsTrue ( test ) )
523
523
{
524
- if ( testConstantBool )
525
- {
526
- testEvaluatesToTrue = true ;
527
- }
528
- else
529
- {
530
- // if test evaluates to 'false' we can remove the WhenClause
531
- RestoreNonNullableColumnsList ( currentNonNullableColumnsCount ) ;
532
- RestoreNullValueColumnsList ( currentNullValueColumnsCount ) ;
524
+ testEvaluatesToTrue = true ;
525
+ }
526
+ else if ( IsFalse ( test ) )
527
+ {
528
+ // if test evaluates to 'false' we can remove the WhenClause
529
+ RestoreNonNullableColumnsList ( currentNonNullableColumnsCount ) ;
530
+ RestoreNullValueColumnsList ( currentNullValueColumnsCount ) ;
533
531
534
- continue ;
535
- }
532
+ continue ;
536
533
}
537
534
538
535
var newResult = Visit ( whenClause . Result , out var resultNullable ) ;
@@ -570,7 +567,7 @@ protected virtual SqlExpression VisitCase(CaseExpression caseExpression, bool al
570
567
// if there is only one When clause and it's test evaluates to 'true' AND there is no else block, simply return the result
571
568
return elseResult == null
572
569
&& whenClauses . Count == 1
573
- && TryGetBoolConstantValue ( whenClauses [ 0 ] . Test ) == true
570
+ && IsTrue ( whenClauses [ 0 ] . Test )
574
571
? whenClauses [ 0 ] . Result
575
572
: caseExpression . Update ( operand , whenClauses , elseResult ) ;
576
573
}
@@ -635,7 +632,7 @@ protected virtual SqlExpression VisitExists(
635
632
636
633
// if subquery has predicate which evaluates to false, we can simply return false
637
634
// if the exists is negated we need to return true instead
638
- return TryGetBoolConstantValue ( subquery . Predicate ) == false
635
+ return IsFalse ( subquery . Predicate )
639
636
? _sqlExpressionFactory . Constant ( false , existsExpression . TypeMapping )
640
637
: existsExpression . Update ( subquery ) ;
641
638
}
@@ -658,7 +655,7 @@ protected virtual SqlExpression VisitIn(InExpression inExpression, bool allowOpt
658
655
var subquery = Visit ( inExpression . Subquery ) ;
659
656
660
657
// a IN (SELECT * FROM table WHERE false) => false
661
- if ( TryGetBoolConstantValue ( subquery . Predicate ) == false )
658
+ if ( IsFalse ( subquery . Predicate ) )
662
659
{
663
660
nullable = false ;
664
661
@@ -967,9 +964,64 @@ protected virtual SqlExpression VisitLike(LikeExpression likeExpression, bool al
967
964
var pattern = Visit ( likeExpression . Pattern , out var patternNullable ) ;
968
965
var escapeChar = Visit ( likeExpression . EscapeChar , out var escapeCharNullable ) ;
969
966
970
- nullable = matchNullable || patternNullable || escapeCharNullable ;
967
+ SqlExpression result = likeExpression . Update ( match , pattern , escapeChar ) ;
968
+
969
+ if ( UseRelationalNulls )
970
+ {
971
+ nullable = matchNullable || patternNullable || escapeCharNullable ;
972
+
973
+ return result ;
974
+ }
975
+
976
+ nullable = false ;
977
+
978
+ // The null semantics behavior we implement for LIKE is that it only returns true when both sides are non-null and match; any other
979
+ // input returns false:
980
+ // foo LIKE f% -> true
981
+ // foo LIKE null -> false
982
+ // null LIKE f% -> false
983
+ // null LIKE null -> false
984
+
985
+ if ( IsNull ( match ) || IsNull ( pattern ) || IsNull ( escapeChar ) )
986
+ {
987
+ return _sqlExpressionFactory . Constant ( false , likeExpression . TypeMapping ) ;
988
+ }
989
+
990
+ // A constant match-all pattern (%) returns true for all cases, except where the match string is null:
991
+ // nullable_foo LIKE % -> foo IS NOT NULL
992
+ // non_nullable_foo LIKE % -> true
993
+ if ( pattern is SqlConstantExpression { Value : "%" } )
994
+ {
995
+ return matchNullable
996
+ ? _sqlExpressionFactory . IsNotNull ( match )
997
+ : _sqlExpressionFactory . Constant ( true , likeExpression . TypeMapping ) ;
998
+ }
971
999
972
- return likeExpression . Update ( match , pattern , escapeChar ) ;
1000
+ if ( ! allowOptimizedExpansion )
1001
+ {
1002
+ if ( matchNullable )
1003
+ {
1004
+ result = _sqlExpressionFactory . AndAlso ( result , GenerateNotNullCheck ( match ) ) ;
1005
+ }
1006
+
1007
+ if ( patternNullable )
1008
+ {
1009
+ result = _sqlExpressionFactory . AndAlso ( result , GenerateNotNullCheck ( pattern ) ) ;
1010
+ }
1011
+
1012
+ if ( escapeChar is not null && escapeCharNullable )
1013
+ {
1014
+ result = _sqlExpressionFactory . AndAlso ( result , GenerateNotNullCheck ( escapeChar ) ) ;
1015
+ }
1016
+ }
1017
+
1018
+ return result ;
1019
+
1020
+ SqlExpression GenerateNotNullCheck ( SqlExpression operand )
1021
+ => OptimizeNonNullableNotExpression (
1022
+ _sqlExpressionFactory . Not (
1023
+ ProcessNullNotNull (
1024
+ _sqlExpressionFactory . IsNull ( operand ) , operandNullable : true ) ) ) ;
973
1025
}
974
1026
975
1027
/// <summary>
@@ -1395,8 +1447,28 @@ protected virtual SqlExpression VisitJsonScalar(
1395
1447
/// </summary>
1396
1448
protected virtual bool PreferExistsToComplexIn => false ;
1397
1449
1398
- private static bool ? TryGetBoolConstantValue ( SqlExpression ? expression )
1399
- => expression is SqlConstantExpression { Value : bool boolValue } ? boolValue : null ;
1450
+ // Note that we can check parameter values for null since we cache by the parameter nullability; but we cannot do the same for bool.
1451
+ private bool IsNull ( SqlExpression ? expression )
1452
+ => expression is SqlConstantExpression { Value : null }
1453
+ || expression is SqlParameterExpression { Name : string parameterName } && ParameterValues [ parameterName ] is null ;
1454
+
1455
+ private bool IsTrue ( SqlExpression ? expression )
1456
+ => expression is SqlConstantExpression { Value : true } ;
1457
+
1458
+ private bool IsFalse ( SqlExpression ? expression )
1459
+ => expression is SqlConstantExpression { Value : false } ;
1460
+
1461
+ private bool TryGetBool ( SqlExpression ? expression , out bool value )
1462
+ {
1463
+ if ( expression is SqlConstantExpression { Value : bool b } )
1464
+ {
1465
+ value = b ;
1466
+ return true ;
1467
+ }
1468
+
1469
+ value = false ;
1470
+ return false ;
1471
+ }
1400
1472
1401
1473
private void RestoreNonNullableColumnsList ( int counter )
1402
1474
{
@@ -1486,7 +1558,7 @@ private SqlExpression OptimizeComparison(
1486
1558
return result ;
1487
1559
}
1488
1560
1489
- if ( TryGetBoolConstantValue ( right ) is bool rightBoolValue
1561
+ if ( TryGetBool ( right , out var rightBoolValue )
1490
1562
&& ! leftNullable
1491
1563
&& left . TypeMapping ! . Converter == null )
1492
1564
{
@@ -1502,7 +1574,7 @@ private SqlExpression OptimizeComparison(
1502
1574
: left ;
1503
1575
}
1504
1576
1505
- if ( TryGetBoolConstantValue ( left ) is bool leftBoolValue
1577
+ if ( TryGetBool ( left , out var leftBoolValue )
1506
1578
&& ! rightNullable
1507
1579
&& right . TypeMapping ! . Converter == null )
1508
1580
{
@@ -2069,10 +2141,6 @@ private SqlExpression ProcessNullNotNull(SqlUnaryExpression sqlUnaryExpression,
2069
2141
private static bool IsLogicalNot ( SqlUnaryExpression ? sqlUnaryExpression )
2070
2142
=> sqlUnaryExpression is { OperatorType : ExpressionType . Not } && sqlUnaryExpression . Type == typeof ( bool ) ;
2071
2143
2072
- private bool IsNull ( SqlExpression expression )
2073
- => expression is SqlConstantExpression { Value : null }
2074
- || expression is SqlParameterExpression { Name : string parameterName } && ParameterValues [ parameterName ] is null ;
2075
-
2076
2144
// ?a == ?b -> [(a == b) && (a != null && b != null)] || (a == null && b == null))
2077
2145
//
2078
2146
// a | b | F1 = a == b | F2 = (a != null && b != null) | F3 = F1 && F2 |
0 commit comments