- 
                Notifications
    You must be signed in to change notification settings 
- Fork 1.3k
CSHARP-5628: Add new boolean expression simplifications to PartialEvaluator #1803
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
| { | ||
| var leftExpression = Visit(node.Left); | ||
| if (leftExpression is ConstantExpression constantLeftExpression ) | ||
| if (node.NodeType == ExpressionType.AndAlso) | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Normally we like to do simplifications in the AstSimplifier but the existing simplifications for && and || were already being handled here.
VisitBinary is essentially the same as before but modified to use the new IsConstant helper method.
| { | ||
| // true ? A : B => A | ||
| // false ? A : B => B | ||
| return testValue ? Visit(node.IfTrue) : Visit(node.IfFalse); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was an existing simplification.
| (d => new R<bool> { N = d.Id, V = d.I1 == E.E1 ? true : false }, "{ $project : { N : '$_id', V : { $eq : ['$I1', 1] }, _id : 0 } }", new[] { true, false }), | ||
| (d => new R<bool> { N = d.Id, V = d.S1 == E.E1 ? true : false }, "{ $project : { N : '$_id', V : { $eq : ['$S1', 'E1'] }, _id : 0 } }", new[] { true, false }), | ||
| (d => new R<bool> { N = d.Id, V = E.E1 == d.I1 ? true : false }, "{ $project : { N : '$_id', V : { $eq : [1, '$I1'] }, _id : 0 } }", new[] { true, false }), | ||
| (d => new R<bool> { N = d.Id, V = E.E1 == d.S1 ? true : false }, "{ $project : { N : '$_id', V : { $eq : ['E1', '$S1'] }, _id : 0 } }", new[] { true, false }) | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the only existing test affected by these new simplifications.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR implements boolean expression simplifications for MongoDB LINQ queries to generate more efficient aggregation pipelines. The changes optimize boolean logic patterns (e.g., x ? true : false → x, !!x → x) in both Select and Where clauses.
Key changes:
- Enhanced PartialEvaluatorto simplify boolean expressions including conditional expressions, logical operators, and double negations
- Updated expected MongoDB aggregation stages in existing tests to reflect simplified outputs
- Added comprehensive test coverage for 23 different boolean simplification patterns
Reviewed Changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 56 comments.
| File | Description | 
|---|---|
| src/MongoDB.Driver/Linq/Linq3Implementation/Misc/PartialEvaluator.cs | Expanded expression visitor with boolean simplification logic for conditionals, binary operations (AndAlso/OrElse), and unary operations (double negation) | 
| tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5628Tests.cs | New test file with comprehensive test cases validating boolean simplifications in Select and Where clauses | 
| tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp4337Tests.cs | Updated test expectations to match simplified pipeline stages (removed redundant $condexpressions) | 
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| var collection = Fixture.Collection; | ||
|  | ||
| // see: https://codeql.github.com/codeql-query-help/csharp/cs-simplifiable-boolean-expression/#recommendation | ||
| // not all simplifciations listed there are safe for a database (because of possibly missing fields or tri-valued logic) | 
    
      
    
      Copilot
AI
    
    
    
      Oct 28, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Corrected spelling of 'simplifciations' to 'simplifications'.
| // not all simplifciations listed there are safe for a database (because of possibly missing fields or tri-valued logic) | |
| // not all simplifications listed there are safe for a database (because of possibly missing fields or tri-valued logic) | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
| { | ||
| if (node.NodeType == ExpressionType.AndAlso) | ||
| var leftExpression = node.Left; | ||
| var rightExpression = node.Right; | 
    
      
    
      Copilot
AI
    
    
    
      Oct 28, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extra space between '=' and 'node.Right'. Should be single space for consistency.
| var rightExpression = node.Right; | |
| var rightExpression = node.Right; | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
|  | ||
| if (node.NodeType == ExpressionType.OrElse) | ||
| var ifTrue = Visit(node.IfTrue); | ||
| var ifFalse = Visit(node.IfFalse); | 
    
      
    
      Copilot
AI
    
    
    
      Oct 28, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extra space between '=' and 'Visit'. Should be single space for consistency.
| var ifFalse = Visit(node.IfFalse); | |
| var ifFalse = Visit(node.IfFalse); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
| ? Visit(Expression.Or(test, ifFalse)) | ||
| : Visit(Expression.And(Expression.Not(test), ifFalse)); | ||
| } | ||
| else if (IsConstant<bool>(ifFalse, out ifFalseValue)) | ||
| { | ||
| // T ? P : true => !T || P | ||
| // T ? P : false => T && P | ||
| return ifFalseValue | ||
| ? Visit(Expression.Or(Expression.Not(test), ifTrue)) | ||
| : Visit(Expression.And(test, ifTrue)); | 
    
      
    
      Copilot
AI
    
    
    
      Oct 28, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using Expression.Or and Expression.And instead of Expression.OrElse and Expression.AndAlso may cause issues. These methods create bitwise operations for integral types, not short-circuit logical operations. Use Expression.OrElse and Expression.AndAlso for boolean logic to ensure proper short-circuit evaluation.
| ? Visit(Expression.Or(test, ifFalse)) | |
| : Visit(Expression.And(Expression.Not(test), ifFalse)); | |
| } | |
| else if (IsConstant<bool>(ifFalse, out ifFalseValue)) | |
| { | |
| // T ? P : true => !T || P | |
| // T ? P : false => T && P | |
| return ifFalseValue | |
| ? Visit(Expression.Or(Expression.Not(test), ifTrue)) | |
| : Visit(Expression.And(test, ifTrue)); | |
| ? Visit(Expression.OrElse(test, ifFalse)) | |
| : Visit(Expression.AndAlso(Expression.Not(test), ifFalse)); | |
| } | |
| else if (IsConstant<bool>(ifFalse, out ifFalseValue)) | |
| { | |
| // T ? P : true => !T || P | |
| // T ? P : false => T && P | |
| return ifFalseValue | |
| ? Visit(Expression.OrElse(Expression.Not(test), ifTrue)) | |
| : Visit(Expression.AndAlso(test, ifTrue)); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
| ? Visit(Expression.Or(test, ifFalse)) | ||
| : Visit(Expression.And(Expression.Not(test), ifFalse)); | ||
| } | ||
| else if (IsConstant<bool>(ifFalse, out ifFalseValue)) | ||
| { | ||
| // T ? P : true => !T || P | ||
| // T ? P : false => T && P | ||
| return ifFalseValue | ||
| ? Visit(Expression.Or(Expression.Not(test), ifTrue)) | ||
| : Visit(Expression.And(test, ifTrue)); | 
    
      
    
      Copilot
AI
    
    
    
      Oct 28, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using Expression.Or and Expression.And instead of Expression.OrElse and Expression.AndAlso may cause issues. These methods create bitwise operations for integral types, not short-circuit logical operations. Use Expression.OrElse and Expression.AndAlso for boolean logic to ensure proper short-circuit evaluation.
| ? Visit(Expression.Or(test, ifFalse)) | |
| : Visit(Expression.And(Expression.Not(test), ifFalse)); | |
| } | |
| else if (IsConstant<bool>(ifFalse, out ifFalseValue)) | |
| { | |
| // T ? P : true => !T || P | |
| // T ? P : false => T && P | |
| return ifFalseValue | |
| ? Visit(Expression.Or(Expression.Not(test), ifTrue)) | |
| : Visit(Expression.And(test, ifTrue)); | |
| ? Visit(Expression.OrElse(test, ifFalse)) | |
| : Visit(Expression.AndAlso(Expression.Not(test), ifFalse)); | |
| } | |
| else if (IsConstant<bool>(ifFalse, out ifFalseValue)) | |
| { | |
| // T ? P : true => !T || P | |
| // T ? P : false => T && P | |
| return ifFalseValue | |
| ? Visit(Expression.OrElse(Expression.Not(test), ifTrue)) | |
| : Visit(Expression.AndAlso(test, ifTrue)); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
| 17 => collection.AsQueryable().Where(x => x.P ? false : false), | ||
|  | ||
| 18 => collection.AsQueryable().Where(x => !(x.X == x.Y)), | ||
| 19 => collection.AsQueryable().Where(x => !(x.X != x.Y)), | 
    
      
    
      Copilot
AI
    
    
    
      Oct 28, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The expression '!(A != B)' can be simplified to 'A == B'.
| 19 => collection.AsQueryable().Where(x => !(x.X != x.Y)), | |
| 19 => collection.AsQueryable().Where(x => x.X == x.Y), | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Won't do because the point is to test that the LINQ translator does this simplification.
|  | ||
| 18 => collection.AsQueryable().Where(x => !(x.X == x.Y)), | ||
| 19 => collection.AsQueryable().Where(x => !(x.X != x.Y)), | ||
| 20 => collection.AsQueryable().Where(x => !(x.X < x.Y)), // not safe | 
    
      
    
      Copilot
AI
    
    
    
      Oct 28, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The expression '!(A < B)' can be simplified to 'A >= B'.
| 20 => collection.AsQueryable().Where(x => !(x.X < x.Y)), // not safe | |
| 20 => collection.AsQueryable().Where(x => x.X >= x.Y), // not safe | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Won't do because the point is to test that the LINQ translator does NOT do this simplification.
| 18 => collection.AsQueryable().Where(x => !(x.X == x.Y)), | ||
| 19 => collection.AsQueryable().Where(x => !(x.X != x.Y)), | ||
| 20 => collection.AsQueryable().Where(x => !(x.X < x.Y)), // not safe | ||
| 21 => collection.AsQueryable().Where(x => !(x.X > x.Y)), // not safe | 
    
      
    
      Copilot
AI
    
    
    
      Oct 28, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The expression '!(A > B)' can be simplified to 'A <= B'.
| 21 => collection.AsQueryable().Where(x => !(x.X > x.Y)), // not safe | |
| 21 => collection.AsQueryable().Where(x => x.X <= x.Y), // not safe | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Won't do because the point is to test that the LINQ translator does NOT do this simplification.
| 19 => collection.AsQueryable().Where(x => !(x.X != x.Y)), | ||
| 20 => collection.AsQueryable().Where(x => !(x.X < x.Y)), // not safe | ||
| 21 => collection.AsQueryable().Where(x => !(x.X > x.Y)), // not safe | ||
| 22 => collection.AsQueryable().Where(x => !(x.X <= x.Y)), // not safe | 
    
      
    
      Copilot
AI
    
    
    
      Oct 28, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The expression '!(A <= B)' can be simplified to 'A > B'.
| 22 => collection.AsQueryable().Where(x => !(x.X <= x.Y)), // not safe | |
| 22 => collection.AsQueryable().Where(x => x.X > x.Y), // not safe | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Won't do because the point is to test that the LINQ translator does NOT do this simplification.
| 20 => collection.AsQueryable().Where(x => !(x.X < x.Y)), // not safe | ||
| 21 => collection.AsQueryable().Where(x => !(x.X > x.Y)), // not safe | ||
| 22 => collection.AsQueryable().Where(x => !(x.X <= x.Y)), // not safe | ||
| 23 => collection.AsQueryable().Where(x => !(x.X >= x.Y)), // not safe | 
    
      
    
      Copilot
AI
    
    
    
      Oct 28, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The expression '!(A >= B)' can be simplified to 'A < B'.
| 23 => collection.AsQueryable().Where(x => !(x.X >= x.Y)), // not safe | |
| 23 => collection.AsQueryable().Where(x => x.X < x.Y), // not safe | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Won't do because the point is to test that the LINQ translator does NOT do this simplification.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
No description provided.