Skip to content

Commit 59c715b

Browse files
committed
CSHARP-5295: Improve error message when subtracting two decimal values stored as strings in the database.
1 parent 81976d1 commit 59c715b

21 files changed

+239
-111
lines changed

src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Expressions/AstUnaryOperator.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,20 @@ public static bool IsAccumulator(this AstUnaryOperator @operator, out AstUnaryAc
102102
}
103103
}
104104

105+
public static bool IsConvertOperator(this AstUnaryOperator @operator)
106+
=> @operator switch
107+
{
108+
AstUnaryOperator.ToBool or
109+
AstUnaryOperator.ToDate or
110+
AstUnaryOperator.ToDecimal or
111+
AstUnaryOperator.ToDouble or
112+
AstUnaryOperator.ToInt or
113+
AstUnaryOperator.ToLong or
114+
AstUnaryOperator.ToObjectId or
115+
AstUnaryOperator.ToString => true,
116+
_ => false
117+
};
118+
105119
public static string Render(this AstUnaryOperator @operator)
106120
{
107121
return @operator switch

src/MongoDB.Driver/Linq/Linq3Implementation/Misc/ConvertHelper.cs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
using MongoDB.Bson;
2020
using MongoDB.Bson.Serialization;
2121
using MongoDB.Bson.Serialization.Serializers;
22+
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions;
23+
using MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators;
2224

2325
namespace MongoDB.Driver.Linq.Linq3Implementation.Misc
2426
{
@@ -149,20 +151,20 @@ public static Expression RemoveConvertToObject(Expression expression)
149151
return expression;
150152
}
151153

152-
public static Expression RemoveWideningConvert(Expression expression)
154+
public static AstExpression RemoveWideningConvert(AggregationExpression translation)
153155
{
154-
if (expression.NodeType == ExpressionType.Convert)
156+
if (translation.Expression is UnaryExpression convertExpression &&
157+
convertExpression.NodeType == ExpressionType.Convert &&
158+
convertExpression.Operand.Type is var sourceType &&
159+
convertExpression.Type is var targetType &&
160+
IsWideningConvert(sourceType, targetType) &&
161+
translation.Ast is AstUnaryExpression astUnaryExpression &&
162+
astUnaryExpression.Operator.IsConvertOperator())
155163
{
156-
var convertExpression = (UnaryExpression)expression;
157-
var sourceType = convertExpression.Operand.Type;
158-
var targetType = expression.Type;
159-
if (IsWideningConvert(sourceType, targetType))
160-
{
161-
return convertExpression.Operand;
162-
}
164+
return astUnaryExpression.Arg;
163165
}
164166

165-
return expression;
167+
return translation.Ast;
166168
}
167169
}
168170
}

src/MongoDB.Driver/Linq/Linq3Implementation/Misc/SerializationHelper.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,17 @@ public static void EnsureRepresentationIsArray(Expression expression, IBsonSeria
3636
}
3737
}
3838

39-
public static void EnsureRepresentationIsNumeric(Expression expression, AggregationExpression translation)
39+
public static void EnsureRepresentationIsNumeric(Expression expression, Expression argumentExpression, AggregationExpression argumentTranslation)
4040
{
41-
EnsureRepresentationIsNumeric(expression, translation.Serializer);
41+
EnsureRepresentationIsNumeric(expression, argumentExpression, argumentTranslation.Serializer);
4242
}
4343

44-
public static void EnsureRepresentationIsNumeric(Expression expression, IBsonSerializer serializer)
44+
public static void EnsureRepresentationIsNumeric(Expression expression, Expression argumentExpression, IBsonSerializer argumentSerializer)
4545
{
46-
var representation = GetRepresentation(serializer);
47-
if (!IsNumericRepresentation(representation))
46+
var argumentRepresentation = GetRepresentation(argumentSerializer);
47+
if (!IsNumericRepresentation(argumentRepresentation))
4848
{
49-
throw new ExpressionNotSupportedException(expression, because: $"serializer for type {serializer.ValueType} uses a non-numeric representation: {representation}");
49+
throw new ExpressionNotSupportedException(expression, because: $"{argumentExpression} uses a non-numeric representation: {argumentRepresentation}");
5050
}
5151
}
5252

src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/BinaryExpressionToAggregationExpressionTranslator.cs

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,6 @@ public static AggregationExpression Translate(TranslationContext context, Binary
5353
throw new ExpressionNotSupportedException(expression, because: "operand types are not compatible with each other");
5454
}
5555

56-
if (IsArithmeticExpression(expression))
57-
{
58-
leftExpression = ConvertHelper.RemoveWideningConvert(leftExpression);
59-
rightExpression = ConvertHelper.RemoveWideningConvert(rightExpression);
60-
}
61-
6256
if (IsEnumExpression(expression))
6357
{
6458
return TranslateEnumExpression(context, expression);
@@ -81,38 +75,43 @@ public static AggregationExpression Translate(TranslationContext context, Binary
8175
rightTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, rightExpression);
8276
}
8377

78+
var leftAst = leftTranslation.Ast;
79+
var rightAst = rightTranslation.Ast;
8480
if (IsArithmeticExpression(expression))
8581
{
86-
SerializationHelper.EnsureRepresentationIsNumeric(leftExpression, leftTranslation);
87-
SerializationHelper.EnsureRepresentationIsNumeric(rightExpression, rightTranslation);
82+
SerializationHelper.EnsureRepresentationIsNumeric(expression, leftExpression, leftTranslation);
83+
SerializationHelper.EnsureRepresentationIsNumeric(expression, rightExpression, rightTranslation);
84+
85+
leftAst = ConvertHelper.RemoveWideningConvert(leftTranslation);
86+
rightAst = ConvertHelper.RemoveWideningConvert(rightTranslation);
8887
}
8988

9089
var ast = expression.NodeType switch
9190
{
92-
ExpressionType.Add => AstExpression.Add(leftTranslation.Ast, rightTranslation.Ast),
91+
ExpressionType.Add => AstExpression.Add(leftAst, rightAst),
9392
ExpressionType.And => expression.Type == typeof(bool) ?
94-
AstExpression.And(leftTranslation.Ast, rightTranslation.Ast) :
95-
AstExpression.BitAnd(leftTranslation.Ast, rightTranslation.Ast),
96-
ExpressionType.AndAlso => AstExpression.And(leftTranslation.Ast, rightTranslation.Ast),
97-
ExpressionType.Coalesce => AstExpression.IfNull(leftTranslation.Ast, rightTranslation.Ast),
98-
ExpressionType.Divide => AstExpression.Divide(leftTranslation.Ast, rightTranslation.Ast),
99-
ExpressionType.Equal => AstExpression.Eq(leftTranslation.Ast, rightTranslation.Ast),
93+
AstExpression.And(leftAst, rightAst) :
94+
AstExpression.BitAnd(leftAst, rightAst),
95+
ExpressionType.AndAlso => AstExpression.And(leftAst, rightAst),
96+
ExpressionType.Coalesce => AstExpression.IfNull(leftAst, rightAst),
97+
ExpressionType.Divide => AstExpression.Divide(leftAst, rightAst),
98+
ExpressionType.Equal => AstExpression.Eq(leftAst, rightAst),
10099
ExpressionType.ExclusiveOr => expression.Type == typeof(bool) ?
101100
throw new ExpressionNotSupportedException(expression, because: "MongoDB does not have an $xor operator") :
102-
AstExpression.BitXor(leftTranslation.Ast, rightTranslation.Ast),
103-
ExpressionType.GreaterThan => AstExpression.Gt(leftTranslation.Ast, rightTranslation.Ast),
104-
ExpressionType.GreaterThanOrEqual => AstExpression.Gte(leftTranslation.Ast, rightTranslation.Ast),
105-
ExpressionType.LessThan => AstExpression.Lt(leftTranslation.Ast, rightTranslation.Ast),
106-
ExpressionType.LessThanOrEqual => AstExpression.Lte(leftTranslation.Ast, rightTranslation.Ast),
107-
ExpressionType.Modulo => AstExpression.Mod(leftTranslation.Ast, rightTranslation.Ast),
108-
ExpressionType.Multiply => AstExpression.Multiply(leftTranslation.Ast, rightTranslation.Ast),
109-
ExpressionType.NotEqual => AstExpression.Ne(leftTranslation.Ast, rightTranslation.Ast),
101+
AstExpression.BitXor(leftAst, rightAst),
102+
ExpressionType.GreaterThan => AstExpression.Gt(leftAst, rightAst),
103+
ExpressionType.GreaterThanOrEqual => AstExpression.Gte(leftAst, rightAst),
104+
ExpressionType.LessThan => AstExpression.Lt(leftAst, rightAst),
105+
ExpressionType.LessThanOrEqual => AstExpression.Lte(leftAst, rightAst),
106+
ExpressionType.Modulo => AstExpression.Mod(leftAst, rightAst),
107+
ExpressionType.Multiply => AstExpression.Multiply(leftAst, rightAst),
108+
ExpressionType.NotEqual => AstExpression.Ne(leftAst, rightAst),
110109
ExpressionType.Or => expression.Type == typeof(bool) ?
111-
AstExpression.Or(leftTranslation.Ast, rightTranslation.Ast) :
112-
AstExpression.BitOr(leftTranslation.Ast, rightTranslation.Ast),
113-
ExpressionType.OrElse => AstExpression.Or(leftTranslation.Ast, rightTranslation.Ast),
114-
ExpressionType.Power => AstExpression.Pow(leftTranslation.Ast, rightTranslation.Ast),
115-
ExpressionType.Subtract => AstExpression.Subtract(leftTranslation.Ast, rightTranslation.Ast),
110+
AstExpression.Or(leftAst, rightAst) :
111+
AstExpression.BitOr(leftAst, rightAst),
112+
ExpressionType.OrElse => AstExpression.Or(leftAst, rightAst),
113+
ExpressionType.Power => AstExpression.Pow(leftAst, rightAst),
114+
ExpressionType.Subtract => AstExpression.Subtract(leftAst, rightAst),
116115
_ => throw new ExpressionNotSupportedException(expression)
117116
};
118117
var serializer = expression.Type switch

src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/AbsMethodToAggregationExpressionTranslator.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,13 @@ public static AggregationExpression Translate(TranslationContext context, Method
4141

4242
if (method.IsOneOf(__absMethods))
4343
{
44-
var valueExpression = ConvertHelper.RemoveWideningConvert(arguments[0]);
44+
var valueExpression = arguments[0];
4545
var valueTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, valueExpression);
46-
SerializationHelper.EnsureRepresentationIsNumeric(valueExpression, valueTranslation);
47-
var ast = AstExpression.Abs(valueTranslation.Ast);
46+
SerializationHelper.EnsureRepresentationIsNumeric(expression, valueExpression, valueTranslation);
47+
48+
var valueAst = ConvertHelper.RemoveWideningConvert(valueTranslation);
49+
var ast = AstExpression.Abs(valueAst);
50+
4851
return new AggregationExpression(expression, ast, valueTranslation.Serializer);
4952
}
5053

src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/CeilingMethodToAggregationExpressionTranslator.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,14 @@ public static AggregationExpression Translate(TranslationContext context, Method
3030

3131
if (method.IsOneOf(MathMethod.CeilingWithDecimal, MathMethod.CeilingWithDouble))
3232
{
33-
var argumentExpression = ConvertHelper.RemoveWideningConvert(arguments[0]);
33+
var argumentExpression = arguments[0];
3434
var argumentTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, argumentExpression);
35-
SerializationHelper.EnsureRepresentationIsNumeric(argumentExpression, argumentTranslation);
36-
var ast = AstExpression.Ceil(argumentTranslation.Ast);
35+
SerializationHelper.EnsureRepresentationIsNumeric(expression, argumentExpression, argumentTranslation);
36+
37+
var argumentAst = ConvertHelper.RemoveWideningConvert(argumentTranslation);
38+
var ast = AstExpression.Ceil(argumentAst);
3739
var serializer = BsonSerializer.LookupSerializer(expression.Type);
40+
3841
return new AggregationExpression(expression, ast, serializer);
3942
}
4043

src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/DateTimeAddOrSubtractMethodToAggregationExpressionTranslator.cs

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,6 @@ public static AggregationExpression Translate(TranslationContext context, Method
126126
}
127127

128128
var thisTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, thisExpression);
129-
valueExpression = ConvertHelper.RemoveWideningConvert(valueExpression);
130129

131130
AstExpression unit, amount;
132131
if (method.IsOneOf(__dateTimeAddOrSubtractWithTimeSpanMethods))
@@ -144,47 +143,50 @@ public static AggregationExpression Translate(TranslationContext context, Method
144143
{
145144
throw new ExpressionNotSupportedException(valueExpression, expression);
146145
}
147-
SerializationHelper.EnsureRepresentationIsNumeric(valueExpression, timeSpanSerializer);
146+
SerializationHelper.EnsureRepresentationIsNumeric(expression, valueExpression, timeSpanSerializer);
148147

148+
var valueAst = ConvertHelper.RemoveWideningConvert(valueTranslation);
149149
var serializerUnits = timeSpanSerializer.Units;
150150
(unit, amount) = serializerUnits switch
151151
{
152-
TimeSpanUnits.Ticks => ("millisecond", AstExpression.Divide(valueTranslation.Ast, (double)TimeSpan.TicksPerMillisecond)),
153-
TimeSpanUnits.Nanoseconds => ("millisecond", AstExpression.Divide(valueTranslation.Ast, 1000000.0)),
154-
TimeSpanUnits.Microseconds => ("millisecond", AstExpression.Divide(valueTranslation.Ast, 1000.0)),
155-
TimeSpanUnits.Milliseconds => ("millisecond", valueTranslation.Ast),
156-
TimeSpanUnits.Seconds => ("second", valueTranslation.Ast),
157-
TimeSpanUnits.Minutes => ("minute", valueTranslation.Ast),
158-
TimeSpanUnits.Hours => ("hour", valueTranslation.Ast),
159-
TimeSpanUnits.Days => ("day", valueTranslation.Ast),
152+
TimeSpanUnits.Ticks => ("millisecond", AstExpression.Divide(valueAst, (double)TimeSpan.TicksPerMillisecond)),
153+
TimeSpanUnits.Nanoseconds => ("millisecond", AstExpression.Divide(valueAst, 1000000.0)),
154+
TimeSpanUnits.Microseconds => ("millisecond", AstExpression.Divide(valueAst, 1000.0)),
155+
TimeSpanUnits.Milliseconds => ("millisecond", valueAst),
156+
TimeSpanUnits.Seconds => ("second", valueAst),
157+
TimeSpanUnits.Minutes => ("minute", valueAst),
158+
TimeSpanUnits.Hours => ("hour", valueAst),
159+
TimeSpanUnits.Days => ("day", valueAst),
160160
_ => throw new ExpressionNotSupportedException(valueExpression, expression)
161161
};
162162
}
163163
}
164164
else if (method.IsOneOf(__dateTimeAddOrSubtractWithUnitMethods))
165165
{
166166
var valueTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, valueExpression);
167+
var valueAst = ConvertHelper.RemoveWideningConvert(valueTranslation);
167168
var unitExpression = arguments[2];
168169
var unitConstant = unitExpression.GetConstantValue<DateTimeUnit>(expression);
169-
(unit, amount) = (unitConstant.Unit, valueTranslation.Ast);
170+
(unit, amount) = (unitConstant.Unit, valueAst);
170171
}
171172
else
172173
{
173174
var valueTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, valueExpression);
174-
SerializationHelper.EnsureRepresentationIsNumeric(valueExpression, valueTranslation);
175+
SerializationHelper.EnsureRepresentationIsNumeric(expression, valueExpression, valueTranslation);
175176

177+
var valueAst = ConvertHelper.RemoveWideningConvert(valueTranslation);
176178
(unit, amount) = method.Name switch
177179
{
178-
"AddTicks" => ("millisecond", AstExpression.Divide(valueTranslation.Ast, (double)TimeSpan.TicksPerMillisecond)),
179-
"AddMilliseconds" => ("millisecond", valueTranslation.Ast),
180-
"AddSeconds" => ("second", valueTranslation.Ast),
181-
"AddMinutes" => ("minute", valueTranslation.Ast),
182-
"AddHours" => ("hour", valueTranslation.Ast),
183-
"AddDays" => ("day", valueTranslation.Ast),
184-
"AddWeeks" => ("week", valueTranslation.Ast),
185-
"AddMonths" => ("month", valueTranslation.Ast),
186-
"AddQuarters" => ("quarter", valueTranslation.Ast),
187-
"AddYears" => ("year", valueTranslation.Ast),
180+
"AddTicks" => ("millisecond", AstExpression.Divide(valueAst, (double)TimeSpan.TicksPerMillisecond)),
181+
"AddMilliseconds" => ("millisecond", valueAst),
182+
"AddSeconds" => ("second", valueAst),
183+
"AddMinutes" => ("minute", valueAst),
184+
"AddHours" => ("hour", valueAst),
185+
"AddDays" => ("day", valueAst),
186+
"AddWeeks" => ("week", valueAst),
187+
"AddMonths" => ("month", valueAst),
188+
"AddQuarters" => ("quarter", valueAst),
189+
"AddYears" => ("year", valueAst),
188190
_ => throw new ExpressionNotSupportedException(expression)
189191
};
190192
}

src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/ExpMethodToAggregationExpressionTranslator.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,13 @@ public static AggregationExpression Translate(TranslationContext context, Method
3030

3131
if (method.Is(MathMethod.Exp))
3232
{
33-
var argumentExpression = ConvertHelper.RemoveWideningConvert(arguments[0]);
33+
var argumentExpression = arguments[0];
3434
var argumentTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, argumentExpression);
35-
SerializationHelper.EnsureRepresentationIsNumeric(argumentExpression, argumentTranslation);
36-
var ast = AstExpression.Exp(argumentTranslation.Ast);
35+
SerializationHelper.EnsureRepresentationIsNumeric(expression, argumentExpression, argumentTranslation);
36+
37+
var argumentAst = ConvertHelper.RemoveWideningConvert(argumentTranslation);
38+
var ast = AstExpression.Exp(argumentAst);
39+
3740
return new AggregationExpression(expression, ast, new DoubleSerializer());
3841
}
3942

src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/FloorMethodToAggregationExpressionTranslator.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,14 @@ public static AggregationExpression Translate(TranslationContext context, Method
3030

3131
if (method.IsOneOf(MathMethod.FloorWithDecimal, MathMethod.FloorWithDouble))
3232
{
33-
var argumentExpression = ConvertHelper.RemoveWideningConvert(arguments[0]);
33+
var argumentExpression = arguments[0];
3434
var argumentTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, argumentExpression);
35-
SerializationHelper.EnsureRepresentationIsNumeric(argumentExpression, argumentTranslation);
36-
var ast = AstExpression.Floor(argumentTranslation.Ast);
35+
SerializationHelper.EnsureRepresentationIsNumeric(expression, argumentExpression, argumentTranslation);
36+
37+
var argumentAst = ConvertHelper.RemoveWideningConvert(argumentTranslation);
38+
var ast = AstExpression.Floor(argumentAst);
3739
var serializer = BsonSerializer.LookupSerializer(expression.Type);
40+
3841
return new AggregationExpression(expression, ast, serializer);
3942
}
4043

0 commit comments

Comments
 (0)