Skip to content

Commit fbcf93a

Browse files
committed
CSHARP-5071: Support string concatenation of mixed types.
1 parent 5282cb0 commit fbcf93a

File tree

5 files changed

+593
-19
lines changed

5 files changed

+593
-19
lines changed

src/MongoDB.Driver/Linq/Linq3Implementation/Reflection/StringMethod.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ internal static class StringMethod
2828
private static readonly MethodInfo __anyStringInWithParams;
2929
private static readonly MethodInfo __anyStringNinWithEnumerable;
3030
private static readonly MethodInfo __anyStringNinWithParams;
31+
private static readonly MethodInfo __concatWith1Object;
32+
private static readonly MethodInfo __concatWith2Objects;
33+
private static readonly MethodInfo __concatWith3Objects;
34+
private static readonly MethodInfo __concatWithObjectArray;
3135
private static readonly MethodInfo __concatWith2Strings;
3236
private static readonly MethodInfo __concatWith3Strings;
3337
private static readonly MethodInfo __concatWith4Strings;
@@ -108,6 +112,10 @@ static StringMethod()
108112
__anyStringInWithParams = ReflectionInfo.Method((IEnumerable<string> s, StringOrRegularExpression[] values) => s.AnyStringIn(values));
109113
__anyStringNinWithEnumerable = ReflectionInfo.Method((IEnumerable<string> s, IEnumerable<StringOrRegularExpression> values) => s.AnyStringNin(values));
110114
__anyStringNinWithParams = ReflectionInfo.Method((IEnumerable<string> s, StringOrRegularExpression[] values) => s.AnyStringNin(values));
115+
__concatWith1Object = ReflectionInfo.Method((object arg) => string.Concat(arg));
116+
__concatWith2Objects = ReflectionInfo.Method((object arg0, object arg1) => string.Concat(arg0, arg1));
117+
__concatWith3Objects = ReflectionInfo.Method((object arg0, object arg1, object arg2) => string.Concat(arg0, arg1, arg2));
118+
__concatWithObjectArray = ReflectionInfo.Method((object[] args) => string.Concat(args));
111119
__concatWith2Strings = ReflectionInfo.Method((string str0, string str1) => string.Concat(str0, str1));
112120
__concatWith3Strings = ReflectionInfo.Method((string str0, string str1, string str2) => string.Concat(str0, str1, str2));
113121
__concatWith4Strings = ReflectionInfo.Method((string str0, string str1, string str2, string str3) => string.Concat(str0, str1, str2, str3));
@@ -168,6 +176,10 @@ static StringMethod()
168176
public static MethodInfo AnyStringInWithParams => __anyStringInWithParams;
169177
public static MethodInfo AnyStringNinWithEnumerable => __anyStringNinWithEnumerable;
170178
public static MethodInfo AnyStringNinWithParams => __anyStringNinWithParams;
179+
public static MethodInfo ConcatWith1Object => __concatWith1Object;
180+
public static MethodInfo ConcatWith2Objects => __concatWith2Objects;
181+
public static MethodInfo ConcatWith3Objects => __concatWith3Objects;
182+
public static MethodInfo ConcatWithObjectArray => __concatWithObjectArray;
171183
public static MethodInfo ConcatWith2Strings => __concatWith2Strings;
172184
public static MethodInfo ConcatWith3Strings => __concatWith3Strings;
173185
public static MethodInfo ConcatWith4Strings => __concatWith4Strings;

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

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
using MongoDB.Driver.Linq.Linq3Implementation.ExtensionMethods;
2323
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
2424
using MongoDB.Driver.Linq.Linq3Implementation.Serializers;
25+
using MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.MethodTranslators;
2526
using MongoDB.Driver.Support;
2627

2728
namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators
@@ -30,6 +31,11 @@ internal static class BinaryExpressionToAggregationExpressionTranslator
3031
{
3132
public static AggregationExpression Translate(TranslationContext context, BinaryExpression expression)
3233
{
34+
if (StringConcatMethodToAggregationExpressionTranslator.CanTranslate(expression, out var method, out var arguments))
35+
{
36+
return StringConcatMethodToAggregationExpressionTranslator.Translate(context, expression, method, arguments);
37+
}
38+
3339
if (GetTypeComparisonExpressionToAggregationExpressionTranslator.CanTranslate(expression))
3440
{
3541
return GetTypeComparisonExpressionToAggregationExpressionTranslator.Translate(context, expression);
@@ -78,9 +84,7 @@ public static AggregationExpression Translate(TranslationContext context, Binary
7884

7985
var ast = expression.NodeType switch
8086
{
81-
ExpressionType.Add => IsStringConcatenationExpression(expression) ?
82-
AstExpression.Concat(leftTranslation.Ast, rightTranslation.Ast) :
83-
AstExpression.Add(leftTranslation.Ast, rightTranslation.Ast),
87+
ExpressionType.Add => AstExpression.Add(leftTranslation.Ast, rightTranslation.Ast),
8488
ExpressionType.And => expression.Type == typeof(bool) ?
8589
AstExpression.And(leftTranslation.Ast, rightTranslation.Ast) :
8690
AstExpression.BitAnd(leftTranslation.Ast, rightTranslation.Ast),
@@ -221,15 +225,6 @@ static bool IsEnumOrConvertEnumToUnderlyingType(Expression expression)
221225
return expression.Type.IsEnum || IsConvertEnumToUnderlyingType(expression);
222226
}
223227

224-
private static bool IsStringConcatenationExpression(BinaryExpression expression)
225-
{
226-
return
227-
expression.NodeType == ExpressionType.Add &&
228-
expression.Type == typeof(string) &&
229-
expression.Left.Type == typeof(string) &&
230-
expression.Right.Type == typeof(string);
231-
}
232-
233228
private static AstBinaryOperator ToBinaryOperator(ExpressionType nodeType)
234229
{
235230
return nodeType switch

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ public static AggregationExpression Translate(TranslationContext context, Method
2626
return EnumerableConcatMethodToAggregationExpressionTranslator.Translate(context, expression);
2727
}
2828

29-
if (StringConcatMethodToAggregationExpressionTranslator.CanTranslate(expression))
29+
if (StringConcatMethodToAggregationExpressionTranslator.CanTranslate(expression, out var method, out var arguments))
3030
{
31-
return StringConcatMethodToAggregationExpressionTranslator.Translate(context, expression);
31+
return StringConcatMethodToAggregationExpressionTranslator.Translate(context, expression, method, arguments);
3232
}
3333

3434
throw new ExpressionNotSupportedException(expression);

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

Lines changed: 94 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@
1414
*/
1515

1616
using System.Collections.Generic;
17+
using System.Collections.ObjectModel;
1718
using System.Linq;
1819
using System.Linq.Expressions;
1920
using System.Reflection;
21+
using MongoDB.Bson;
22+
using MongoDB.Bson.IO;
2023
using MongoDB.Bson.Serialization.Serializers;
2124
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions;
2225
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
@@ -27,20 +30,48 @@ internal static class StringConcatMethodToAggregationExpressionTranslator
2730
{
2831
private static readonly MethodInfo[] __stringConcatMethods = new[]
2932
{
33+
StringMethod.ConcatWith1Object,
34+
StringMethod.ConcatWith2Objects,
35+
StringMethod.ConcatWith3Objects,
36+
StringMethod.ConcatWithObjectArray,
3037
StringMethod.ConcatWith2Strings,
3138
StringMethod.ConcatWith3Strings,
3239
StringMethod.ConcatWith4Strings,
3340
StringMethod.ConcatWithStringArray
3441
};
3542

36-
public static bool CanTranslate(MethodCallExpression expression)
37-
=> expression.Method.IsOneOf(__stringConcatMethods);
43+
public static bool CanTranslate(BinaryExpression expression, out MethodInfo method, out ReadOnlyCollection<Expression> arguments)
44+
{
45+
if (expression.NodeType == ExpressionType.Add &&
46+
expression.Method != null &&
47+
expression.Method.IsOneOf(StringMethod.ConcatWith2Objects, StringMethod.ConcatWith2Strings))
48+
{
49+
method = expression.Method;
50+
arguments = new ReadOnlyCollection<Expression>(new[] { expression.Left, expression.Right });
51+
return true;
52+
}
53+
54+
method = null;
55+
arguments = null;
56+
return false;
57+
}
3858

39-
public static AggregationExpression Translate(TranslationContext context, MethodCallExpression expression)
59+
public static bool CanTranslate(MethodCallExpression expression, out MethodInfo method, out ReadOnlyCollection<Expression> arguments)
4060
{
41-
var method = expression.Method;
42-
var arguments = expression.Arguments;
61+
if (expression.Method.IsOneOf(__stringConcatMethods))
62+
{
63+
method = expression.Method;
64+
arguments = expression.Arguments;
65+
return true;
66+
}
4367

68+
method = null;
69+
arguments = null;
70+
return false;
71+
}
72+
73+
public static AggregationExpression Translate(TranslationContext context, Expression expression, MethodInfo method, ReadOnlyCollection<Expression> arguments)
74+
{
4475
IEnumerable<AstExpression> argumentsTranslations = null;
4576

4677
if (method.IsOneOf(
@@ -52,6 +83,16 @@ public static AggregationExpression Translate(TranslationContext context, Method
5283
arguments.Select(a => ExpressionToAggregationExpressionTranslator.Translate(context, a).Ast);
5384
}
5485

86+
if (method.IsOneOf(
87+
StringMethod.ConcatWith1Object,
88+
StringMethod.ConcatWith2Objects,
89+
StringMethod.ConcatWith3Objects))
90+
{
91+
argumentsTranslations = arguments
92+
.Select(a => ExpressionToAggregationExpressionTranslator.Translate(context, a))
93+
.Select(ExpressionToString);
94+
}
95+
5596
if (method.Is(StringMethod.ConcatWithStringArray))
5697
{
5798
var argumentTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, arguments.Single());
@@ -61,13 +102,61 @@ public static AggregationExpression Translate(TranslationContext context, Method
61102
}
62103
}
63104

105+
if (method.Is(StringMethod.ConcatWithObjectArray))
106+
{
107+
if (arguments.Single() is NewArrayExpression newArrayExpression)
108+
{
109+
argumentsTranslations = newArrayExpression.Expressions
110+
.Select(a => ExpressionToAggregationExpressionTranslator.Translate(context, a))
111+
.Select(ExpressionToString);
112+
}
113+
}
114+
64115
if (argumentsTranslations != null)
65116
{
66117
var ast = AstExpression.Concat(argumentsTranslations.ToArray());
67118
return new AggregationExpression(expression, ast, StringSerializer.Instance);
68119
}
69120

70121
throw new ExpressionNotSupportedException(expression);
122+
123+
static AstExpression ExpressionToString(AggregationExpression aggregationExpression)
124+
{
125+
var astExpression = aggregationExpression.Ast;
126+
if (aggregationExpression.Serializer.ValueType == typeof(string))
127+
{
128+
return astExpression;
129+
}
130+
else
131+
{
132+
if (astExpression is AstConstantExpression constantAstExpression)
133+
{
134+
var value = constantAstExpression.Value;
135+
var stringValue = ValueToString(aggregationExpression.Expression, value);
136+
return AstExpression.Constant(stringValue);
137+
}
138+
else
139+
{
140+
return AstExpression.ToString(astExpression);
141+
}
142+
}
143+
}
144+
145+
static string ValueToString(Expression expression, BsonValue value)
146+
{
147+
return value switch
148+
{
149+
BsonBoolean booleanValue => JsonConvert.ToString(booleanValue.Value),
150+
BsonDateTime dateTimeValue => JsonConvert.ToString(dateTimeValue.ToUniversalTime()),
151+
BsonDecimal128 decimalValue => JsonConvert.ToString(decimalValue.Value),
152+
BsonDouble doubleValue => JsonConvert.ToString(doubleValue.Value),
153+
BsonInt32 int32Value => JsonConvert.ToString(int32Value.Value),
154+
BsonInt64 int64Value => JsonConvert.ToString(int64Value.Value),
155+
BsonObjectId objectIdValue => objectIdValue.Value.ToString(),
156+
BsonString stringValue => stringValue.Value,
157+
_ => throw new ExpressionNotSupportedException(expression, because: $"values represented as BSON type {value.BsonType} are not supported by $toString")
158+
};
159+
}
71160
}
72161
}
73162
}

0 commit comments

Comments
 (0)