Skip to content

Commit f6158ef

Browse files
rstamdnickless
authored andcommitted
CSHARP-4410: Test full support for enums in aggregation expressions.
1 parent d16d06a commit f6158ef

File tree

15 files changed

+739
-122
lines changed

15 files changed

+739
-122
lines changed

src/MongoDB.Bson/Serialization/IRepresentationConfigurable.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,19 @@ namespace MongoDB.Bson.Serialization
1818
/// <summary>
1919
/// Represents a serializer that has a Representation property.
2020
/// </summary>
21-
public interface IRepresentationConfigurable
21+
public interface IHasRepresentationSerializer
2222
{
2323
/// <summary>
2424
/// Gets the representation.
2525
/// </summary>
26-
/// <value>
27-
/// The representation.
28-
/// </value>
2926
BsonType Representation { get; }
27+
}
3028

29+
/// <summary>
30+
/// Represents a serializer whose representation can be configured.
31+
/// </summary>
32+
public interface IRepresentationConfigurable : IHasRepresentationSerializer
33+
{
3134
/// <summary>
3235
/// Returns a serializer that has been reconfigured with the specified representation.
3336
/// </summary>

src/MongoDB.Bson/Serialization/Serializers/EnumSerializer.cs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ public EnumSerializer(BsonType representation)
6565
throw new BsonSerializationException(message);
6666
}
6767

68+
if (representation == 0)
69+
{
70+
representation = GetRepresentationForUnderlyingType();
71+
}
72+
6873
_representation = representation;
6974
_underlyingTypeCode = Type.GetTypeCode(Enum.GetUnderlyingType(typeof(TEnum)));
7075
}
@@ -128,16 +133,6 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati
128133

129134
switch (_representation)
130135
{
131-
case 0:
132-
if (_underlyingTypeCode == TypeCode.Int64 || _underlyingTypeCode == TypeCode.UInt64)
133-
{
134-
goto case BsonType.Int64;
135-
}
136-
else
137-
{
138-
goto case BsonType.Int32;
139-
}
140-
141136
case BsonType.Int32:
142137
bsonWriter.WriteInt32(ConvertEnumToInt32(value));
143138
break;
@@ -162,6 +157,11 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati
162157
/// <returns>The reconfigured serializer.</returns>
163158
public EnumSerializer<TEnum> WithRepresentation(BsonType representation)
164159
{
160+
if (representation == 0)
161+
{
162+
representation = GetRepresentationForUnderlyingType();
163+
}
164+
165165
if (representation == _representation)
166166
{
167167
return this;
@@ -257,6 +257,12 @@ private TEnum ConvertInt64ToEnum(long value)
257257
}
258258
}
259259

260+
private BsonType GetRepresentationForUnderlyingType()
261+
{
262+
var underlyingType = Enum.GetUnderlyingType(typeof(TEnum));
263+
return (underlyingType == typeof(long) || underlyingType == typeof(ulong)) ? BsonType.Int64 : BsonType.Int32;
264+
}
265+
260266
private TEnum ConvertStringToEnum(string value)
261267
{
262268
if (Enum.TryParse<TEnum>(value, ignoreCase: false, out var result))

src/MongoDB.Bson/Serialization/Serializers/NullableGenericSerializer.cs

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,46 @@
1414
*/
1515

1616
using System;
17-
using MongoDB.Bson.IO;
18-
using MongoDB.Bson.Serialization.Attributes;
1917

2018
namespace MongoDB.Bson.Serialization.Serializers
2119
{
20+
/// <summary>
21+
/// An interface used by the LINQ3 translators to access the value serializer without needing to use reflection.
22+
/// </summary>
23+
public interface INullableSerializer
24+
{
25+
/// <summary>
26+
/// Gets the value serializer.
27+
/// </summary>
28+
IBsonSerializer ValueSerializer { get; }
29+
}
30+
31+
/// <summary>
32+
/// Static factory class for NullableSerializers.
33+
/// </summary>
34+
public static class NullableSerializer
35+
{
36+
/// <summary>
37+
/// Creates a NullableSerializer.
38+
/// </summary>
39+
/// <param name="valueSerializer">The value serializer.</param>
40+
/// <returns>A NullableSerializer</returns>
41+
public static IBsonSerializer Create(IBsonSerializer valueSerializer)
42+
{
43+
var valueType = valueSerializer.ValueType;
44+
var nullableSerializerType = typeof(NullableSerializer<>).MakeGenericType(valueType);
45+
return (IBsonSerializer)Activator.CreateInstance(nullableSerializerType, valueSerializer);
46+
}
47+
}
48+
2249
/// <summary>
2350
/// Represents a serializer for nullable values.
2451
/// </summary>
2552
/// <typeparam name="T">The underlying type.</typeparam>
2653
public class NullableSerializer<T> :
2754
SerializerBase<Nullable<T>>,
28-
IChildSerializerConfigurable
55+
IChildSerializerConfigurable,
56+
INullableSerializer
2957
where T : struct
3058
{
3159
// private fields
@@ -68,6 +96,15 @@ public NullableSerializer(IBsonSerializerRegistry serializerRegistry)
6896
_lazySerializer = new Lazy<IBsonSerializer<T>>(() => serializerRegistry.GetSerializer<T>());
6997
}
7098

99+
// public properties
100+
/// <summary>
101+
/// Gets the value serializer.
102+
/// </summary>
103+
public IBsonSerializer<T> ValueSerializer => _lazySerializer.Value;
104+
105+
// explicitly implemented properties
106+
IBsonSerializer INullableSerializer.ValueSerializer => ValueSerializer;
107+
71108
// public methods
72109
/// <summary>
73110
/// Deserializes a value.
@@ -91,6 +128,17 @@ public NullableSerializer(IBsonSerializerRegistry serializerRegistry)
91128
}
92129
}
93130

131+
/// <inheritdoc/>
132+
public override bool Equals(object obj)
133+
{
134+
return
135+
obj is NullableSerializer<T> other &&
136+
ValueSerializer.Equals(other.ValueSerializer);
137+
}
138+
139+
/// <inheritdoc/>
140+
public override int GetHashCode() => ValueSerializer.GetHashCode();
141+
94142
/// <summary>
95143
/// Serializes a value.
96144
/// </summary>

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public static Expression RemoveConvertToMongoQueryable(Expression expression)
8282
{
8383
var convertExpression = (UnaryExpression)expression;
8484
var convertToType = convertExpression.Type;
85-
if (convertToType.IsGenericType() &&
85+
if (convertToType.IsGenericType &&
8686
convertToType.GetGenericTypeDefinition() == typeof(IMongoQueryable<>))
8787
{
8888
return convertExpression.Operand;
@@ -99,7 +99,9 @@ public static Expression RemoveConvertToEnumUnderlyingType(Expression expression
9999
var convertExpression = (UnaryExpression)expression;
100100
var sourceType = convertExpression.Operand.Type;
101101
var targetType = convertExpression.Type;
102-
if (sourceType.IsEnum() && targetType == Enum.GetUnderlyingType(sourceType))
102+
103+
if (sourceType.IsEnumOrNullableEnum(out _, out var underlyingType) &&
104+
targetType.IsSameAsOrNullableOf(underlyingType))
103105
{
104106
return convertExpression.Operand;
105107
}

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

Lines changed: 75 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public static bool Implements(this Type type, Type @interface)
3737
return true;
3838
}
3939

40-
if (type.IsGenericType() && type.GetGenericTypeDefinition() == @interface)
40+
if (type.IsGenericType && type.GetGenericTypeDefinition() == @interface)
4141
{
4242
return true;
4343
}
@@ -49,7 +49,7 @@ public static bool Implements(this Type type, Type @interface)
4949
return true;
5050
}
5151

52-
if (implementedInterface.IsGenericType() && implementedInterface.GetGenericTypeDefinition() == @interface)
52+
if (implementedInterface.IsGenericType && implementedInterface.GetGenericTypeDefinition() == @interface)
5353
{
5454
return true;
5555
}
@@ -65,7 +65,7 @@ public static bool Is(this Type type, Type comparand)
6565
return true;
6666
}
6767

68-
if (type.IsGenericType() && comparand.IsGenericTypeDefinition())
68+
if (type.IsGenericType && comparand.IsGenericTypeDefinition)
6969
{
7070
if (type.GetGenericTypeDefinition() == comparand)
7171
{
@@ -76,26 +76,89 @@ public static bool Is(this Type type, Type comparand)
7676
return false;
7777
}
7878

79-
public static bool IsEnum(this Type type)
79+
public static bool IsEnum(this Type type, out Type underlyingType)
8080
{
81-
return type.IsEnum;
81+
if (type.IsEnum)
82+
{
83+
underlyingType = Enum.GetUnderlyingType(type);
84+
return true;
85+
}
86+
else
87+
{
88+
underlyingType = null;
89+
return false;
90+
}
91+
}
92+
93+
public static bool IsEnum(this Type type, out Type enumType, out Type underlyingType)
94+
{
95+
if (type.IsEnum)
96+
{
97+
enumType = type;
98+
underlyingType = Enum.GetUnderlyingType(type);
99+
return true;
100+
}
101+
else
102+
{
103+
enumType = null;
104+
underlyingType = null;
105+
return false;
106+
}
107+
}
108+
109+
public static bool IsEnumOrNullableEnum(this Type type, out Type enumType, out Type underlyingType)
110+
{
111+
return
112+
type.IsEnum(out enumType, out underlyingType) ||
113+
type.IsNullableEnum(out enumType, out underlyingType);
114+
}
115+
116+
public static bool IsNullable(this Type type)
117+
{
118+
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
119+
}
120+
121+
public static bool IsNullable(this Type type, out Type valueType)
122+
{
123+
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
124+
{
125+
valueType = type.GetGenericArguments()[0];
126+
return true;
127+
}
128+
else
129+
{
130+
valueType = null;
131+
return false;
132+
}
133+
}
134+
135+
public static bool IsNullableEnum(this Type type)
136+
{
137+
return type.IsNullable(out var valueType) && valueType.IsEnum;
138+
}
139+
140+
public static bool IsNullableEnum(this Type type, out Type enumType, out Type underlyingType)
141+
{
142+
enumType = null;
143+
underlyingType = null;
144+
return type.IsNullable(out var valueType) && valueType.IsEnum(out enumType, out underlyingType);
82145
}
83146

84-
public static bool IsGenericType(this Type type)
147+
public static bool IsNullableOf(this Type type, Type valueType)
85148
{
86-
return type.IsGenericType;
149+
return type.IsNullable(out var nullableValueType) && nullableValueType == valueType;
87150
}
88151

89-
public static bool IsGenericTypeDefinition(this Type type)
152+
public static bool IsSameAsOrNullableOf(this Type type, Type valueType)
90153
{
91-
return type.IsGenericType;
154+
return type == valueType || type.IsNullableOf(valueType);
92155
}
93156

94157
public static bool TryGetIDictionaryGenericInterface(this Type type, out Type idictionaryGenericInterface)
95158
{
96159
foreach (var interfaceType in type.GetInterfaces())
97160
{
98-
if (interfaceType.IsGenericType() && interfaceType.GetGenericTypeDefinition() == typeof(IDictionary<,>))
161+
if (interfaceType.IsGenericType && interfaceType.GetGenericTypeDefinition() == typeof(IDictionary<,>))
99162
{
100163
idictionaryGenericInterface = interfaceType;
101164
return true;
@@ -108,15 +171,15 @@ public static bool TryGetIDictionaryGenericInterface(this Type type, out Type id
108171

109172
public static bool TryGetIEnumerableGenericInterface(this Type type, out Type ienumerableGenericInterface)
110173
{
111-
if (type.IsGenericType() && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
174+
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
112175
{
113176
ienumerableGenericInterface = type;
114177
return true;
115178
}
116179

117180
foreach (var interfaceType in type.GetInterfaces())
118181
{
119-
if (interfaceType.IsGenericType() && interfaceType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
182+
if (interfaceType.IsGenericType && interfaceType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
120183
{
121184
ienumerableGenericInterface = interfaceType;
122185
return true;

src/MongoDB.Driver/Linq/Linq3Implementation/Serializers/EnumUnderlyingTypeSerializer.cs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,20 @@
1414
*/
1515

1616
using System;
17+
using MongoDB.Bson;
1718
using MongoDB.Bson.Serialization;
1819
using MongoDB.Bson.Serialization.Serializers;
1920
using MongoDB.Driver.Core.Misc;
2021
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
2122

2223
namespace MongoDB.Driver.Linq.Linq3Implementation.Serializers
2324
{
24-
internal class EnumUnderlyingTypeSerializer<TEnum, TEnumUnderlyingType> : StructSerializerBase<TEnumUnderlyingType>
25+
internal interface IEnumUnderlyingTypeSerializer
26+
{
27+
IBsonSerializer EnumSerializer { get; }
28+
}
29+
30+
internal class EnumUnderlyingTypeSerializer<TEnum, TEnumUnderlyingType> : StructSerializerBase<TEnumUnderlyingType>, IEnumUnderlyingTypeSerializer
2531
where TEnum : Enum
2632
where TEnumUnderlyingType : struct
2733
{
@@ -41,13 +47,27 @@ public EnumUnderlyingTypeSerializer(IBsonSerializer<TEnum> enumSerializer)
4147
// public properties
4248
public IBsonSerializer<TEnum> EnumSerializer => _enumSerializer;
4349

50+
// explicitly implemented properties
51+
IBsonSerializer IEnumUnderlyingTypeSerializer.EnumSerializer => EnumSerializer;
52+
4453
// public methods
4554
public override TEnumUnderlyingType Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
4655
{
4756
var enumValue = _enumSerializer.Deserialize(context);
4857
return (TEnumUnderlyingType)(object)enumValue;
4958
}
5059

60+
/// <inheritdoc/>
61+
public override bool Equals(object obj)
62+
{
63+
return
64+
obj is EnumUnderlyingTypeSerializer<TEnum, TEnumUnderlyingType> other &&
65+
_enumSerializer.Equals(other._enumSerializer);
66+
}
67+
68+
/// <inheritdoc/>
69+
public override int GetHashCode() => _enumSerializer.GetHashCode();
70+
5171
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TEnumUnderlyingType value)
5272
{
5373
var enumValue = (TEnum)(object)value;
@@ -60,9 +80,9 @@ internal static class EnumUnderlyingTypeSerializer
6080
public static IBsonSerializer Create(IBsonSerializer enumSerializer)
6181
{
6282
var enumType = enumSerializer.ValueType;
63-
var enumUnderlyingType = enumType.GetEnumUnderlyingType();
64-
var serializerType = typeof(EnumUnderlyingTypeSerializer<,>).MakeGenericType(enumType, enumUnderlyingType);
65-
return (IBsonSerializer)Activator.CreateInstance(serializerType, enumSerializer);
83+
var underlyingType = Enum.GetUnderlyingType(enumType);
84+
var enumUnderlyingTypeSerializerType = typeof(EnumUnderlyingTypeSerializer<,>).MakeGenericType(enumType, underlyingType);
85+
return (IBsonSerializer)Activator.CreateInstance(enumUnderlyingTypeSerializerType, enumSerializer);
6686
}
6787
}
6888
}

0 commit comments

Comments
 (0)