Skip to content

Commit c9263ab

Browse files
committed
CSHARP-4397: Translate conversions to short form like $toInt instead of $convert when possible.
1 parent ecc4e6e commit c9263ab

File tree

3 files changed

+191
-19
lines changed

3 files changed

+191
-19
lines changed

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

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -240,26 +240,34 @@ public static AstExpression Constant(BsonValue value)
240240

241241
public static AstExpression Convert(AstExpression input, AstExpression to, AstExpression onError = null, AstExpression onNull = null)
242242
{
243-
return new AstConvertExpression(input, to, onError, onNull);
244-
}
245-
246-
public static AstExpression Convert(AstExpression input, Type toType, AstExpression onError = null, AstExpression onNull = null)
247-
{
248-
Ensure.IsNotNull(toType, nameof(toType));
249-
var to = toType.FullName switch
243+
Ensure.IsNotNull(input, nameof(input));
244+
Ensure.IsNotNull(to, nameof(to));
245+
246+
if (to is AstConstantExpression toConstantExpression &&
247+
(toConstantExpression.Value as BsonString)?.Value is string toValue &&
248+
toValue != null &&
249+
onError == null &&
250+
onNull == null)
250251
{
251-
"MongoDB.Bson.ObjectId" => "objectId",
252-
"System.Boolean" => "bool",
253-
"System.DateTime" => "date",
254-
"System.Decimal" => "decimal",
255-
"System.Double" => "double",
256-
"System.Int32" => "int",
257-
"System.Int64" => "long",
258-
"System.String" => "string",
259-
_ => throw new ArgumentException($"Invalid toType: {toType.FullName}.", nameof(toType))
260-
};
252+
var unaryOperator = toValue switch
253+
{
254+
"bool" => AstUnaryOperator.ToBool,
255+
"date" => AstUnaryOperator.ToDate,
256+
"decimal" => AstUnaryOperator.ToDecimal,
257+
"double" => AstUnaryOperator.ToDouble,
258+
"int" => AstUnaryOperator.ToInt,
259+
"long" => AstUnaryOperator.ToLong,
260+
"objectId" => AstUnaryOperator.ToObjectId,
261+
"string" => AstUnaryOperator.ToString,
262+
_ => (AstUnaryOperator?)null
263+
};
264+
if (unaryOperator.HasValue)
265+
{
266+
return AstExpression.Unary(unaryOperator.Value, input);
267+
}
268+
}
261269

262-
return AstExpression.Convert(input, to, onError, onNull);
270+
return new AstConvertExpression(input, to, onError, onNull);
263271
}
264272

265273
public static AstExpression DateAdd(
@@ -834,6 +842,11 @@ public static AstExpression Trunc(AstExpression arg)
834842
return new AstUnaryExpression(AstUnaryOperator.Trunc, arg);
835843
}
836844

845+
public static AstExpression Unary(AstUnaryOperator @operator, AstExpression arg)
846+
{
847+
return new AstUnaryExpression(@operator, arg);
848+
}
849+
837850
public static AstAccumulatorExpression UnaryAccumulator(AstUnaryAccumulatorOperator @operator, AstExpression arg)
838851
{
839852
return new AstUnaryAccumulatorExpression(@operator, arg);

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,20 @@ public static AggregationExpression Translate(TranslationContext context, UnaryE
6363
}
6464
else
6565
{
66-
ast = AstExpression.Convert(ast, expressionType);
66+
var to = expressionType.FullName switch
67+
{
68+
"MongoDB.Bson.ObjectId" => "objectId",
69+
"System.Boolean" => "bool",
70+
"System.DateTime" => "date",
71+
"System.Decimal" => "decimal",
72+
"System.Double" => "double",
73+
"System.Int32" => "int",
74+
"System.Int64" => "long",
75+
"System.String" => "string",
76+
_ => throw new ExpressionNotSupportedException(expression, because: $"conversion to {expressionType} is not supported")
77+
};
78+
79+
ast = AstExpression.Convert(ast, to);
6780
serializer = context.KnownSerializersRegistry.GetSerializer(expression);
6881
}
6982

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using FluentAssertions;
17+
using MongoDB.Bson;
18+
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions;
19+
using Xunit;
20+
21+
namespace MongoDB.Driver.Tests.Linq.Linq3ImplementationTests.Ast.Expressions
22+
{
23+
public class AstExpressionTests
24+
{
25+
[Theory]
26+
[InlineData("bool", (int)AstUnaryOperator.ToBool)]
27+
[InlineData("date", (int)AstUnaryOperator.ToDate)]
28+
[InlineData("decimal", (int)AstUnaryOperator.ToDecimal)]
29+
[InlineData("double", (int)AstUnaryOperator.ToDouble)]
30+
[InlineData("int", (int)AstUnaryOperator.ToInt)]
31+
[InlineData("long", (int)AstUnaryOperator.ToLong)]
32+
[InlineData("objectId", (int)AstUnaryOperator.ToObjectId)]
33+
[InlineData("string", (int)AstUnaryOperator.ToString)]
34+
public void Convert_with_to_constant_should_return_short_form_when_possible(string toValue, int expectedOperator)
35+
{
36+
var input = AstExpression.Constant(BsonNull.Value);
37+
var to = AstExpression.Constant(toValue);
38+
39+
var result = AstExpression.Convert(input, to, onError: null, onNull: null);
40+
41+
var unaryExpression = result.Should().BeOfType<AstUnaryExpression>().Subject;
42+
unaryExpression.Operator.Should().Be((AstUnaryOperator)expectedOperator);
43+
unaryExpression.Arg.Should().BeSameAs(input);
44+
}
45+
46+
[Theory]
47+
[InlineData("xyz")]
48+
public void Convert_with_to_constant_should_return_long_form_when_necessary(string toValue)
49+
{
50+
var input = AstExpression.Constant(BsonNull.Value);
51+
var to = AstExpression.Constant(toValue);
52+
53+
var result = AstExpression.Convert(input, to, onError: null, onNull: null);
54+
55+
var convertExpression = result.Should().BeOfType<AstConvertExpression>().Subject;
56+
convertExpression.Input.Should().BeSameAs(input);
57+
convertExpression.To.Should().BeSameAs(to);
58+
convertExpression.OnError.Should().BeNull();
59+
convertExpression.OnNull.Should().BeNull();
60+
}
61+
62+
[Theory]
63+
[InlineData("bool")]
64+
[InlineData("date")]
65+
[InlineData("decimal")]
66+
[InlineData("double")]
67+
[InlineData("int")]
68+
[InlineData("long")]
69+
[InlineData("objectId")]
70+
[InlineData("string")]
71+
public void Convert_with_to_expression_should_return_long_form(string toValue)
72+
{
73+
var input = AstExpression.Constant(BsonNull.Value);
74+
var to = AstExpression.FieldPath("$To");
75+
76+
var result = AstExpression.Convert(input, to, onError: null, onNull: null);
77+
78+
var convertExpression = result.Should().BeOfType<AstConvertExpression>().Subject;
79+
convertExpression.Input.Should().BeSameAs(input);
80+
convertExpression.To.Should().BeSameAs(to);
81+
convertExpression.OnError.Should().BeNull();
82+
convertExpression.OnNull.Should().BeNull();
83+
}
84+
85+
[Theory]
86+
[InlineData("bool")]
87+
[InlineData("date")]
88+
[InlineData("decimal")]
89+
[InlineData("double")]
90+
[InlineData("int")]
91+
[InlineData("long")]
92+
[InlineData("objectId")]
93+
[InlineData("string")]
94+
public void Convert_with_on_error_should_return_long_form(string toValue)
95+
{
96+
var input = AstExpression.Constant(BsonNull.Value);
97+
var to = AstExpression.Constant(toValue);
98+
var onError = AstExpression.Constant(BsonNull.Value);
99+
100+
var result = AstExpression.Convert(input, to, onError, onNull: null);
101+
102+
var convertExpression = result.Should().BeOfType<AstConvertExpression>().Subject;
103+
convertExpression.Input.Should().BeSameAs(input);
104+
convertExpression.To.Should().BeSameAs(to);
105+
convertExpression.OnError.Should().BeSameAs(onError);
106+
convertExpression.OnNull.Should().BeNull();
107+
}
108+
109+
[Theory]
110+
[InlineData("bool")]
111+
[InlineData("date")]
112+
[InlineData("decimal")]
113+
[InlineData("double")]
114+
[InlineData("int")]
115+
[InlineData("long")]
116+
[InlineData("objectId")]
117+
[InlineData("string")]
118+
public void Convert_with_on_null_should_return_long_form(string toValue)
119+
{
120+
var input = AstExpression.Constant(BsonNull.Value);
121+
var to = AstExpression.Constant(toValue);
122+
var onNull = AstExpression.Constant(BsonNull.Value);
123+
124+
var result = AstExpression.Convert(input, to, onError: null, onNull);
125+
126+
var convertExpression = result.Should().BeOfType<AstConvertExpression>().Subject;
127+
convertExpression.Input.Should().BeSameAs(input);
128+
convertExpression.To.Should().BeSameAs(to);
129+
convertExpression.OnError.Should().BeNull();
130+
convertExpression.OnNull.Should().BeSameAs(onNull);
131+
}
132+
133+
[Fact]
134+
public void Unary_should_return_expected_result()
135+
{
136+
var @operator = AstUnaryOperator.Abs;
137+
var arg = AstExpression.Constant(-1);
138+
139+
var result = AstExpression.Unary(@operator, arg);
140+
141+
var unaryExpression = result.Should().BeOfType<AstUnaryExpression>().Subject;
142+
unaryExpression.Operator.Should().Be(@operator);
143+
unaryExpression.Arg.Should().BeSameAs(arg);
144+
}
145+
}
146+
}

0 commit comments

Comments
 (0)