CSHARP-5889: Optimize comparison with nullable constant translation#1885
CSHARP-5889: Optimize comparison with nullable constant translation#1885sanych-sun wants to merge 2 commits intomongodb:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR optimizes the comparison expression translation for nullable boolean constants in the MongoDB C# Driver's LINQ3 implementation. The changes extend existing boolean comparison optimizations to also handle nullable boolean types.
Changes:
- Modified the comparison translator to recognize nullable boolean comparisons in addition to non-nullable boolean comparisons
- Added comprehensive unit tests to verify the optimization works correctly for both nullable and non-nullable integer/boolean field comparisons with constant values
- Implemented test cases covering all comparison operators (eq, gt, gte, lt, lte) for both field-to-constant and constant-to-field orderings
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| ComparisonExpressionToFilterTranslator.cs | Extended boolean constant comparison optimization to support nullable boolean types (bool?) in addition to regular booleans |
| ComparisonExpressionToFilterTranslatorTests.cs | Added comprehensive unit tests for comparison operations with nullable and non-nullable constant values across all comparison operators |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| rightExpression.Type == typeof(bool)) | ||
| (leftExpression.Type == typeof(bool) || leftExpression.Type == typeof(bool?))) | ||
| { | ||
| return TranslateComparisonToBooleanConstant(context, expression, leftExpression, comparisonOperator, (bool)comparandExpression.Value); |
There was a problem hiding this comment.
The cast to bool on line 89 may throw an InvalidCastException when comparandExpression.Value is null for nullable bool comparisons. When leftExpression.Type is typeof(bool?), the constant expression could have a null value. Consider adding a null check or using a safer cast pattern (e.g., comparandExpression.Value as bool? ?? false, or checking comparandExpression.Value for null before casting).
| return TranslateComparisonToBooleanConstant(context, expression, leftExpression, comparisonOperator, (bool)comparandExpression.Value); | |
| // Only apply the boolean-constant optimization when the comparand is a non-null boolean. | |
| // For nullable bool comparisons with a null constant, fall through to the general logic below. | |
| if (!(leftExpression.Type == typeof(bool?) && comparandExpression.Value == null)) | |
| { | |
| return TranslateComparisonToBooleanConstant( | |
| context, | |
| expression, | |
| leftExpression, | |
| comparisonOperator, | |
| (bool)comparandExpression.Value); | |
| } |
| CreateTestCase<MyModel>(CreateLambda<MyModel, int>(x => x.Int), Expression.Constant(5), ExpressionType.LessThanOrEqual, AstFilter.Compare(AstFilter.Field("Int"), AstComparisonFilterOperator.Lte, 5)), | ||
| CreateTestCase<MyModel>(CreateLambda<MyModel, int?>(x => x.NullableInt), Expression.Constant(5, typeof(int?)), ExpressionType.LessThanOrEqual, AstFilter.Compare(AstFilter.Field("NullableInt"), AstComparisonFilterOperator.Lte, 5)), | ||
| CreateTestCase<MyModel>(Expression.Constant(5), CreateLambda<MyModel, int>(x => x.Int), ExpressionType.LessThanOrEqual, AstFilter.Compare(AstFilter.Field("Int"), AstComparisonFilterOperator.Gte, 5)), | ||
| CreateTestCase<MyModel>(Expression.Constant(5, typeof(int?)), CreateLambda<MyModel, int?>(x => x.NullableInt), ExpressionType.LessThanOrEqual, AstFilter.Compare(AstFilter.Field("NullableInt"), AstComparisonFilterOperator.Gte, 5)), |
There was a problem hiding this comment.
The test cases do not cover nullable bool comparisons to null constants. Given the optimization now includes bool? type support, test cases should verify behavior when comparing nullable bool fields to null values (e.g., x.NullableBool == null). This is important to ensure the optimization handles null correctly.
| CreateTestCase<MyModel>(Expression.Constant(5, typeof(int?)), CreateLambda<MyModel, int?>(x => x.NullableInt), ExpressionType.LessThanOrEqual, AstFilter.Compare(AstFilter.Field("NullableInt"), AstComparisonFilterOperator.Gte, 5)), | |
| CreateTestCase<MyModel>(Expression.Constant(5, typeof(int?)), CreateLambda<MyModel, int?>(x => x.NullableInt), ExpressionType.LessThanOrEqual, AstFilter.Compare(AstFilter.Field("NullableInt"), AstComparisonFilterOperator.Gte, 5)), | |
| // Comparison to null constant test cases: bool? | |
| CreateTestCase<MyModel>(CreateLambda<MyModel, bool?>(x => x.NullableBool), Expression.Constant(null, typeof(bool?)), ExpressionType.Equal, AstFilter.Eq(AstFilter.Field("NullableBool"), BsonNull.Value)), | |
| CreateTestCase<MyModel>(Expression.Constant(null, typeof(bool?)), CreateLambda<MyModel, bool?>(x => x.NullableBool), ExpressionType.Equal, AstFilter.Eq(AstFilter.Field("NullableBool"), BsonNull.Value)), |
...sionToFilterTranslators/ExpressionTranslators/ComparisonExpressionToFilterTranslatorTests.cs
Outdated
Show resolved
Hide resolved
| public int Int {get; set; } | ||
|
|
||
| public int? NullableInt {get; set; } |
There was a problem hiding this comment.
The properties in the MyModel class are missing a space before the opening brace. According to the codebase convention, property declarations should have a space between "set;" and the closing brace (e.g., "get; set; }" instead of "get; set; }"). This inconsistency can be seen in other test files throughout the codebase.
| public int Int {get; set; } | |
| public int? NullableInt {get; set; } | |
| public int Int { get; set; } | |
| public int? NullableInt { get; set; } |
| resultAst.Render().Should().Be(BsonDocument.Parse(expectedTranslation)); | ||
| } | ||
|
|
||
| // TODO: Probably need to be merged with CSharp2422Tests and CSharp4401Tests. |
There was a problem hiding this comment.
We have tests to cover comparison translation at least in 2 more test classes: CSharp2422Tests and CSharp4401Tests. Should we move that tests into this file?
No description provided.