@@ -161,7 +161,7 @@ protected virtual TableExpressionBase Visit(TableExpressionBase tableExpressionB
161161                var  newTable  =  Visit ( innerJoinExpression . Table ) ; 
162162                var  newJoinPredicate  =  ProcessJoinPredicate ( innerJoinExpression . JoinPredicate ) ; 
163163
164-                 return  TryGetBoolConstantValue ( newJoinPredicate )   ==   true 
164+                 return  IsTrue ( newJoinPredicate ) 
165165                    ?  new  CrossJoinExpression ( newTable ) 
166166                    :  innerJoinExpression . Update ( newTable ,  newJoinPredicate ) ; 
167167            } 
@@ -301,7 +301,7 @@ protected virtual SelectExpression Visit(SelectExpression selectExpression)
301301        var  predicate  =  Visit ( selectExpression . Predicate ,  allowOptimizedExpansion :  true ,  out  _ ) ; 
302302        changed  |=  predicate  !=  selectExpression . Predicate ; 
303303
304-         if  ( TryGetBoolConstantValue ( predicate )   ==   true ) 
304+         if  ( IsTrue ( predicate ) ) 
305305        { 
306306            predicate  =  null ; 
307307            changed  =  true ; 
@@ -333,7 +333,7 @@ protected virtual SelectExpression Visit(SelectExpression selectExpression)
333333        var  having  =  Visit ( selectExpression . Having ,  allowOptimizedExpansion :  true ,  out  _ ) ; 
334334        changed  |=  having  !=  selectExpression . Having ; 
335335
336-         if  ( TryGetBoolConstantValue ( having )   ==   true ) 
336+         if  ( IsTrue ( having ) ) 
337337        { 
338338            having  =  null ; 
339339            changed  =  true ; 
@@ -519,20 +519,17 @@ protected virtual SqlExpression VisitCase(CaseExpression caseExpression, bool al
519519            var  test  =  Visit ( 
520520                whenClause . Test ,  allowOptimizedExpansion :  testIsCondition ,  preserveColumnNullabilityInformation :  true ,  out  _ ) ; 
521521
522-             if  ( TryGetBoolConstantValue ( test )   is   bool   testConstantBool ) 
522+             if  ( IsTrue ( test ) ) 
523523            { 
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 ) ; 
533531
534-                     continue ; 
535-                 } 
532+                 continue ; 
536533            } 
537534
538535            var  newResult  =  Visit ( whenClause . Result ,  out  var  resultNullable ) ; 
@@ -570,7 +567,7 @@ protected virtual SqlExpression VisitCase(CaseExpression caseExpression, bool al
570567        // if there is only one When clause and it's test evaluates to 'true' AND there is no else block, simply return the result 
571568        return  elseResult  ==  null 
572569            &&  whenClauses . Count  ==  1 
573-             &&  TryGetBoolConstantValue ( whenClauses [ 0 ] . Test )   ==   true 
570+             &&  IsTrue ( whenClauses [ 0 ] . Test ) 
574571                ?  whenClauses [ 0 ] . Result 
575572                :  caseExpression . Update ( operand ,  whenClauses ,  elseResult ) ; 
576573    } 
@@ -635,7 +632,7 @@ protected virtual SqlExpression VisitExists(
635632
636633        // if subquery has predicate which evaluates to false, we can simply return false 
637634        // if the exists is negated we need to return true instead 
638-         return  TryGetBoolConstantValue ( subquery . Predicate )   ==   false 
635+         return  IsFalse ( subquery . Predicate ) 
639636            ?  _sqlExpressionFactory . Constant ( false ,  existsExpression . TypeMapping ) 
640637            :  existsExpression . Update ( subquery ) ; 
641638    } 
@@ -658,7 +655,7 @@ protected virtual SqlExpression VisitIn(InExpression inExpression, bool allowOpt
658655            var  subquery  =  Visit ( inExpression . Subquery ) ; 
659656
660657            // a IN (SELECT * FROM table WHERE false) => false 
661-             if  ( TryGetBoolConstantValue ( subquery . Predicate )   ==   false ) 
658+             if  ( IsFalse ( subquery . Predicate ) ) 
662659            { 
663660                nullable  =  false ; 
664661
@@ -967,9 +964,64 @@ protected virtual SqlExpression VisitLike(LikeExpression likeExpression, bool al
967964        var  pattern  =  Visit ( likeExpression . Pattern ,  out  var  patternNullable ) ; 
968965        var  escapeChar  =  Visit ( likeExpression . EscapeChar ,  out  var  escapeCharNullable ) ; 
969966
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+         } 
971999
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 ) ) ) ; 
9731025    } 
9741026
9751027    /// <summary> 
@@ -1395,8 +1447,28 @@ protected virtual SqlExpression VisitJsonScalar(
13951447    /// </summary> 
13961448    protected  virtual  bool  PreferExistsToComplexIn  =>  false ; 
13971449
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+     } 
14001472
14011473    private  void  RestoreNonNullableColumnsList ( int  counter ) 
14021474    { 
@@ -1486,7 +1558,7 @@ private SqlExpression OptimizeComparison(
14861558            return  result ; 
14871559        } 
14881560
1489-         if  ( TryGetBoolConstantValue ( right )   is   bool  rightBoolValue 
1561+         if  ( TryGetBool ( right ,   out   var  rightBoolValue ) 
14901562            &&  ! leftNullable 
14911563            &&  left . TypeMapping ! . Converter  ==  null ) 
14921564        { 
@@ -1502,7 +1574,7 @@ private SqlExpression OptimizeComparison(
15021574                :  left ; 
15031575        } 
15041576
1505-         if  ( TryGetBoolConstantValue ( left )   is   bool  leftBoolValue 
1577+         if  ( TryGetBool ( left ,   out   var  leftBoolValue ) 
15061578            &&  ! rightNullable 
15071579            &&  right . TypeMapping ! . Converter  ==  null ) 
15081580        { 
@@ -2069,10 +2141,6 @@ private SqlExpression ProcessNullNotNull(SqlUnaryExpression sqlUnaryExpression,
20692141    private  static   bool  IsLogicalNot ( SqlUnaryExpression ?  sqlUnaryExpression ) 
20702142        =>  sqlUnaryExpression  is  {  OperatorType :  ExpressionType . Not  }  &&  sqlUnaryExpression . Type  ==  typeof ( bool ) ; 
20712143
2072-     private  bool  IsNull ( SqlExpression  expression ) 
2073-         =>  expression  is  SqlConstantExpression  {  Value :  null  } 
2074-             ||  expression  is  SqlParameterExpression  {  Name :  string  parameterName  }  &&  ParameterValues [ parameterName ]  is  null ; 
2075- 
20762144    // ?a == ?b -> [(a == b) && (a != null && b != null)] || (a == null && b == null)) 
20772145    // 
20782146    // a | b | F1 = a == b | F2 = (a != null && b != null) | F3 = F1 && F2 | 
0 commit comments