Skip to content

Commit bd9bd3e

Browse files
committed
CSHARP-5572: Implement new KnownSerializerFinder.
1 parent 3669ba7 commit bd9bd3e

File tree

87 files changed

+7127
-258
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

87 files changed

+7127
-258
lines changed

src/MongoDB.Bson/Serialization/IBsonSerializerExtensions.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,38 @@ public static TValue Deserialize<TValue>(this IBsonSerializer<TValue> serializer
5050
return serializer.Deserialize(context, args);
5151
}
5252

53+
/// <summary>
54+
/// Gets the serializer for a base type starting from a serializer for a derived type.
55+
/// </summary>
56+
/// <param name="serializer">The serializer for the derived type.</param>
57+
/// <param name="baseType">The base type.</param>
58+
/// <returns>The serializer for the base type.</returns>
59+
public static IBsonSerializer GetBaseTypeSerializer(this IBsonSerializer serializer, Type baseType)
60+
{
61+
if (!baseType.IsAssignableFrom(serializer.ValueType))
62+
{
63+
throw new ArgumentException($"{baseType} is not assignable from {serializer.ValueType}.");
64+
}
65+
66+
return BsonSerializer.LookupSerializer(baseType); // TODO: should be able to navigate from serializer
67+
}
68+
69+
/// <summary>
70+
/// Gets the serializer for a derived type starting from a serializer for a base type.
71+
/// </summary>
72+
/// <param name="serializer">The serializer for the base type.</param>
73+
/// <param name="derivedType">The derived type.</param>
74+
/// <returns>The serializer for the derived type.</returns>
75+
public static IBsonSerializer GetDerivedTypeSerializer(this IBsonSerializer serializer, Type derivedType)
76+
{
77+
if (!serializer.ValueType.IsAssignableFrom(derivedType))
78+
{
79+
throw new ArgumentException($"{serializer.ValueType} is not assignable from {derivedType}.");
80+
}
81+
82+
return BsonSerializer.LookupSerializer(derivedType); // TODO: should be able to navigate from serializer
83+
}
84+
5385
/// <summary>
5486
/// Gets the discriminator convention for a serializer.
5587
/// </summary>

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

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,73 @@ public interface INullableSerializer
3333
/// </summary>
3434
public static class NullableSerializer
3535
{
36+
private readonly static IBsonSerializer __nullableBooleanInstance = new NullableSerializer<bool>(BooleanSerializer.Instance);
37+
private readonly static IBsonSerializer __nullableDecimalInstance = new NullableSerializer<decimal>(DecimalSerializer.Instance);
38+
private readonly static IBsonSerializer __nullableDecimal128Instance = new NullableSerializer<Decimal128>(Decimal128Serializer.Instance);
39+
private readonly static IBsonSerializer __nullableDoubleInstance = new NullableSerializer<double>(DoubleSerializer.Instance);
40+
private readonly static IBsonSerializer __nullableInt32Instance = new NullableSerializer<int>(Int32Serializer.Instance);
41+
private readonly static IBsonSerializer __nullableInt64Instance = new NullableSerializer<long>(Int64Serializer.Instance);
42+
private readonly static IBsonSerializer __nullableLocalDateTimeInstance = new NullableSerializer<DateTime>(DateTimeSerializer.LocalInstance);
43+
private readonly static IBsonSerializer __nullableObjectIdInstance = new NullableSerializer<ObjectId>(ObjectIdSerializer.Instance);
44+
private readonly static IBsonSerializer __nullableSingleInstance = new NullableSerializer<float>(SingleSerializer.Instance);
45+
private readonly static IBsonSerializer __nullableStandardGuidInstance = new NullableSerializer<Guid>(GuidSerializer.StandardInstance);
46+
private readonly static IBsonSerializer __nullableUtcDateTimeInstance = new NullableSerializer<DateTime>(DateTimeSerializer.UtcInstance);
47+
48+
/// <summary>
49+
/// Gets a serializer for nullable bools.
50+
/// </summary>
51+
public static IBsonSerializer NullableBooleanInstance => __nullableBooleanInstance;
52+
53+
/// <summary>
54+
/// Gets a serializer for nullable decimals.
55+
/// </summary>
56+
public static IBsonSerializer NullableDecimalInstance => __nullableDecimalInstance;
57+
58+
/// <summary>
59+
/// Gets a serializer for nullable Decimal128s.
60+
/// </summary>
61+
public static IBsonSerializer NullableDecimal128Instance => __nullableDecimal128Instance;
62+
63+
/// <summary>
64+
/// Gets a serializer for nullable doubles.
65+
/// </summary>
66+
public static IBsonSerializer NullableDoubleInstance => __nullableDoubleInstance;
67+
68+
/// <summary>
69+
/// Gets a serializer for nullable ints.
70+
/// </summary>
71+
public static IBsonSerializer NullableInt32Instance => __nullableInt32Instance;
72+
73+
/// <summary>
74+
/// Gets a serializer for nullable longs.
75+
/// </summary>
76+
public static IBsonSerializer NullableInt64Instance => __nullableInt64Instance;
77+
78+
/// <summary>
79+
/// Gets a serializer for local DateTime.
80+
/// </summary>
81+
public static IBsonSerializer NullableLocalDateTimeInstance => __nullableLocalDateTimeInstance;
82+
83+
/// <summary>
84+
/// Gets a serializer for nullable floats.
85+
/// </summary>
86+
public static IBsonSerializer NullableSingleInstance => __nullableSingleInstance;
87+
88+
/// <summary>
89+
/// Gets a serializer for nullable ObjectIds.
90+
/// </summary>
91+
public static IBsonSerializer NullableObjectIdInstance => __nullableObjectIdInstance;
92+
93+
/// <summary>
94+
/// Gets a serializer for nullable Guids with standard representation.
95+
/// </summary>
96+
public static IBsonSerializer NullableStandardGuidInstance => __nullableStandardGuidInstance;
97+
98+
/// <summary>
99+
/// Gets a serializer for UTC DateTime.
100+
/// </summary>
101+
public static IBsonSerializer NullableUtcDateTimeInstance => __nullableUtcDateTimeInstance;
102+
36103
/// <summary>
37104
/// Creates a NullableSerializer.
38105
/// </summary>

src/MongoDB.Driver/Linq/Linq3Implementation/ExtensionMethods/ExpressionExtensions.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,5 +60,17 @@ public static TValue GetConstantValue<TValue>(this Expression expression, Expres
6060
var message = $"Expression must be a constant: {expression} in {containingExpression}.";
6161
throw new ExpressionNotSupportedException(message);
6262
}
63+
64+
public static bool IsConvert(this Expression expression, out Expression operand)
65+
{
66+
if (expression is UnaryExpression { NodeType: ExpressionType.Convert } unaryExpression)
67+
{
68+
operand = unaryExpression.Operand;
69+
return true;
70+
}
71+
72+
operand = null;
73+
return false;
74+
}
6375
}
6476
}

src/MongoDB.Driver/Linq/Linq3Implementation/GroupingWithOutputExpressionStageDefinitions.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ private AstStage RenderProjectStage(
6464
out IBsonSerializer<TOutput> outputSerializer)
6565
{
6666
var partiallyEvaluatedOutput = (Expression<Func<TGrouping, TOutput>>)PartialEvaluator.EvaluatePartially(_output);
67-
var context = TranslationContext.Create(translationOptions);
67+
var parameter = partiallyEvaluatedOutput.Parameters.Single();
68+
var context = TranslationContext.Create(partiallyEvaluatedOutput, initialNode: parameter, initialSerializer: inputSerializer, translationOptions: translationOptions);
6869
var outputTranslation = ExpressionToAggregationExpressionTranslator.TranslateLambdaBody(context, partiallyEvaluatedOutput, inputSerializer, asRoot: true);
6970
var (projectStage, projectSerializer) = ProjectionHelper.CreateProjectStage(outputTranslation);
7071
outputSerializer = (IBsonSerializer<TOutput>)projectSerializer;
@@ -106,7 +107,8 @@ protected override AstStage RenderGroupingStage(
106107
out IBsonSerializer<IGrouping<TValue, TInput>> groupingOutputSerializer)
107108
{
108109
var partiallyEvaluatedGroupBy = (Expression<Func<TInput, TValue>>)PartialEvaluator.EvaluatePartially(_groupBy);
109-
var context = TranslationContext.Create(translationOptions);
110+
var parameter = partiallyEvaluatedGroupBy.Parameters.Single();
111+
var context = TranslationContext.Create(partiallyEvaluatedGroupBy, initialNode: parameter, initialSerializer: inputSerializer, translationOptions: translationOptions);
110112
var groupByTranslation = ExpressionToAggregationExpressionTranslator.TranslateLambdaBody(context, partiallyEvaluatedGroupBy, inputSerializer, asRoot: true);
111113

112114
var valueSerializer = (IBsonSerializer<TValue>)groupByTranslation.Serializer;
@@ -150,7 +152,8 @@ protected override AstStage RenderGroupingStage(
150152
out IBsonSerializer<IGrouping<AggregateBucketAutoResultId<TValue>, TInput>> groupingOutputSerializer)
151153
{
152154
var partiallyEvaluatedGroupBy = (Expression<Func<TInput, TValue>>)PartialEvaluator.EvaluatePartially(_groupBy);
153-
var context = TranslationContext.Create(translationOptions);
155+
var parameter = partiallyEvaluatedGroupBy.Parameters.Single();
156+
var context = TranslationContext.Create(partiallyEvaluatedGroupBy, initialNode: parameter, initialSerializer: inputSerializer, translationOptions: translationOptions);
154157
var groupByTranslation = ExpressionToAggregationExpressionTranslator.TranslateLambdaBody(context, partiallyEvaluatedGroupBy, inputSerializer, asRoot: true);
155158

156159
var valueSerializer = (IBsonSerializer<TValue>)groupByTranslation.Serializer;
@@ -188,7 +191,8 @@ protected override AstStage RenderGroupingStage(
188191
out IBsonSerializer<IGrouping<TValue, TInput>> groupingOutputSerializer)
189192
{
190193
var partiallyEvaluatedGroupBy = (Expression<Func<TInput, TValue>>)PartialEvaluator.EvaluatePartially(_groupBy);
191-
var context = TranslationContext.Create(translationOptions);
194+
var parameter = partiallyEvaluatedGroupBy.Parameters.Single();
195+
var context = TranslationContext.Create(partiallyEvaluatedGroupBy, initialNode: parameter, initialSerializer: inputSerializer, translationOptions: translationOptions);
192196
var groupByTranslation = ExpressionToAggregationExpressionTranslator.TranslateLambdaBody(context, partiallyEvaluatedGroupBy, inputSerializer, asRoot: true);
193197
var pushElements = AstExpression.AccumulatorField("_elements", AstUnaryAccumulatorOperator.Push, AstExpression.RootVar);
194198
var groupBySerializer = (IBsonSerializer<TValue>)groupByTranslation.Serializer;
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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 System.Linq.Expressions;
17+
using MongoDB.Bson.Serialization;
18+
19+
namespace MongoDB.Driver.Linq.Linq3Implementation.KnownSerializerFinders;
20+
21+
internal static class KnownSerializerFinder
22+
{
23+
public static KnownSerializerMap FindKnownSerializers(
24+
Expression expression)
25+
{
26+
var knownSerializers = new KnownSerializerMap();
27+
return FindKnownSerializers(expression, knownSerializers);
28+
}
29+
30+
public static KnownSerializerMap FindKnownSerializers(
31+
Expression expression,
32+
Expression initialNode,
33+
IBsonSerializer knownSerializer)
34+
{
35+
var knownSerializers = new KnownSerializerMap();
36+
knownSerializers.AddSerializer(initialNode, knownSerializer);
37+
return FindKnownSerializers(expression, knownSerializers);
38+
}
39+
40+
public static KnownSerializerMap FindKnownSerializers(
41+
Expression expression,
42+
(Expression Node, IBsonSerializer KnownSerializer)[] initialNodes)
43+
{
44+
var knownSerializers = new KnownSerializerMap();
45+
foreach (var (initialNode, knownSerializer) in initialNodes)
46+
{
47+
knownSerializers.AddSerializer(initialNode, knownSerializer);
48+
49+
}
50+
return FindKnownSerializers(expression, knownSerializers);
51+
}
52+
53+
public static KnownSerializerMap FindKnownSerializers(
54+
Expression expression,
55+
KnownSerializerMap knownSerializers)
56+
{
57+
var visitor = new KnownSerializerFinderVisitor(knownSerializers);
58+
59+
int oldSerializerCount;
60+
int newSerializerCount;
61+
do
62+
{
63+
visitor.StartNextPass();
64+
oldSerializerCount = knownSerializers.Count;
65+
visitor.Visit(expression);
66+
newSerializerCount = knownSerializers.Count;
67+
68+
// TODO: prevent infinite loop, throw after 100000 passes?
69+
}
70+
while (visitor.Pass == 1 || newSerializerCount > oldSerializerCount); // I don't know yet if this can be done in a single pass
71+
72+
//#if DEBUG
73+
var expressionWithUnknownSerializer = UnknownSerializerFinder.FindExpressionWithUnknownSerializer(expression, knownSerializers);
74+
if (expressionWithUnknownSerializer != null)
75+
{
76+
throw new ExpressionNotSupportedException(expressionWithUnknownSerializer, because: "we were unable to determine which serializer to use for the result");
77+
}
78+
//#endif
79+
80+
return knownSerializers;
81+
}
82+
}

0 commit comments

Comments
 (0)