-
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 $cond expressions) |
💡 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.
tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5628Tests.cs
Show resolved
Hide resolved
tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5628Tests.cs
Show resolved
Hide resolved
tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5628Tests.cs
Show resolved
Hide resolved
tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5628Tests.cs
Show resolved
Hide resolved
tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5628Tests.cs
Show resolved
Hide resolved
adelinowona
left a comment
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
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 + Minor comments. Also should we unit-test the new translations done by the PartialEvaluator?
| { | ||
| // true && Q => Q | ||
| // false && Q => false | ||
| return leftValue ? Visit(rightExpression) : Expression.Constant(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.
Minor: can save a little on allocation probably by returning leftExpression instead of Expression.Constant(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.
Using leftExpression is a bit subtle. It requires understanding why that works.
Instead I added __falseConstantExpression and __trueConstantExpression fields that can be used for both left and right sides.
| { | ||
| // P && true => P | ||
| // P && false => false | ||
| return rightValue ? leftExpression : Expression.Constant(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.
Minor: same as above can return rightExpression instead of Expression.Constant(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.
Used __falseConstantExpression and __trueConstantExpression instead.
| { | ||
| // true || Q => true | ||
| // false || Q => Q | ||
| return leftValue ? Expression.Constant(true) : Visit(rightExpression); |
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.
Same as above.
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.
Used __falseConstantExpression and __trueConstantExpression instead.
| { | ||
| // P || true => true | ||
| // P || false => P | ||
| return rightValue ? Expression.Constant(true) : leftExpression; |
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.
Same as above.
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.
Used __falseConstantExpression and __trueConstantExpression instead.
No description provided.