CSHARP-5658: Convert value type to match field serializer type in Equals filter translation#1981
CSHARP-5658: Convert value type to match field serializer type in Equals filter translation#1981kyra-rk wants to merge 4 commits intomongodb:mainfrom
Conversation
|
Other filter translators have the same
If this fix looks good, it might be worth extracting the conversion into a shared helper and applying it to these as well. |
There was a problem hiding this comment.
Pull request overview
Adds support for translating Equals filters when the constant value’s CLR type differs from the field serializer’s value type (notably numeric/nullable numeric scenarios), and introduces integration tests covering the regression.
Changes:
- Update
EqualsMethodToFilterTranslatorto coerce constant values to the field serializer’sValueTypebefore serialization. - Add LINQ integration tests for
Nullable<int>fields compared againstulong,int, andnull, including a no-match case.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/EqualsMethodToFilterTranslator.cs |
Adds a pre-serialization constant conversion step based on the field serializer value type. |
tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/EqualsMethodToFilterTranslatorTests.cs |
New integration tests validating translation/output for Equals with numeric and null constants. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (value != null && !serializerValueType.IsInstanceOfType(value)) | ||
| { | ||
| var targetType = Nullable.GetUnderlyingType(serializerValueType) ?? serializerValueType; | ||
| value = Convert.ChangeType(value, targetType); |
There was a problem hiding this comment.
The new Convert.ChangeType call can throw (InvalidCastException/OverflowException/FormatException) and will currently bubble out of translation without context. Please catch conversion failures here and rethrow an ExpressionNotSupportedException tied to the original expression so users get a consistent LINQ translation error.
| value = Convert.ChangeType(value, targetType); | |
| try | |
| { | |
| value = Convert.ChangeType(value, targetType); | |
| } | |
| catch (InvalidCastException) | |
| { | |
| throw new ExpressionNotSupportedException(expression); | |
| } | |
| catch (OverflowException) | |
| { | |
| throw new ExpressionNotSupportedException(expression); | |
| } | |
| catch (FormatException) | |
| { | |
| throw new ExpressionNotSupportedException(expression); | |
| } |
| var targetType = Nullable.GetUnderlyingType(serializerValueType) ?? serializerValueType; | ||
| value = Convert.ChangeType(value, targetType); | ||
| } | ||
|
|
||
| var serializedValue = SerializationHelper.SerializeValue(fieldTranslation.Serializer, value); | ||
| return AstFilter.Eq(fieldTranslation.Ast, serializedValue); | ||
| } |
There was a problem hiding this comment.
Conversion is attempted for any value/type mismatch, which can introduce unintended/culture-dependent conversions (e.g., string->number) rather than failing translation. Consider restricting this conversion to known-safe cases (e.g., numeric/char and nullable numeric via ConvertHelper/TypeExtensions) and leaving other mismatches to fail predictably.
| var targetType = Nullable.GetUnderlyingType(serializerValueType) ?? serializerValueType; | |
| value = Convert.ChangeType(value, targetType); | |
| } | |
| var serializedValue = SerializationHelper.SerializeValue(fieldTranslation.Serializer, value); | |
| return AstFilter.Eq(fieldTranslation.Ast, serializedValue); | |
| } | |
| if (!TryConvertKnownSafeValue(value, serializerValueType, out value)) | |
| { | |
| throw new ExpressionNotSupportedException(expression); | |
| } | |
| } | |
| var serializedValue = SerializationHelper.SerializeValue(fieldTranslation.Serializer, value); | |
| return AstFilter.Eq(fieldTranslation.Ast, serializedValue); | |
| } | |
| private static bool TryConvertKnownSafeValue(object value, Type serializerValueType, out object convertedValue) | |
| { | |
| var sourceType = value.GetType(); | |
| var targetType = Nullable.GetUnderlyingType(serializerValueType) ?? serializerValueType; | |
| if (targetType.IsInstanceOfType(value)) | |
| { | |
| convertedValue = value; | |
| return true; | |
| } | |
| if (IsNumericOrCharType(sourceType) && IsNumericOrCharType(targetType)) | |
| { | |
| convertedValue = Convert.ChangeType(value, targetType); | |
| return true; | |
| } | |
| convertedValue = null; | |
| return false; | |
| } | |
| private static bool IsNumericOrCharType(Type type) | |
| { | |
| type = Nullable.GetUnderlyingType(type) ?? type; | |
| switch (Type.GetTypeCode(type)) | |
| { | |
| case TypeCode.Byte: | |
| case TypeCode.SByte: | |
| case TypeCode.Int16: | |
| case TypeCode.UInt16: | |
| case TypeCode.Int32: | |
| case TypeCode.UInt32: | |
| case TypeCode.Int64: | |
| case TypeCode.UInt64: | |
| case TypeCode.Single: | |
| case TypeCode.Double: | |
| case TypeCode.Decimal: | |
| case TypeCode.Char: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } |
| var queryable = collection.AsQueryable() | ||
| .Where(e => e.ReportsTo.Equals(longPrm)); | ||
|
|
There was a problem hiding this comment.
These tests cover field.Equals(constant) but not the supported shape where the constant is the receiver (constant.Equals(field)), which exercises the translator's constant/field swapping logic with the new conversion behavior. Adding at least one test for that form would help prevent regressions.
No description provided.