-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
CSHARP-5375: Remove KnownSerializerRegistry. #1552
base: main
Are you sure you want to change the base?
Conversation
@@ -32,6 +32,7 @@ internal static class PartialEvaluator | |||
typeof(DateTimeExtensions), | |||
typeof(LinqExtensions), | |||
typeof(MongoEnumerable), | |||
typeof(Mql), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To keep Mql.Constant(...)
from being evaluated client-side.
@@ -102,7 +103,7 @@ public static AggregationExpression Translate(TranslationContext context, Method | |||
var funcLambda = ExpressionHelper.UnquoteLambdaIfQueryableMethod(method, arguments[2]); | |||
var funcParameters = funcLambda.Parameters; | |||
var accumulatorParameter = funcParameters[0]; | |||
var accumulatorSerializer = context.KnownSerializersRegistry.GetSerializer(accumulatorParameter); | |||
var accumulatorSerializer = BsonSerializer.LookupSerializer(accumulatorParameter.Type); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might make sense to have a factory for numeric return types to protect ourselves from users registering weird serializers for numeric types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually the previous comment is moot. What we should be doing is using the serializer from the seedTranslation
as the serializer for the accumulator.
} | ||
else | ||
{ | ||
throw new ExpressionNotSupportedException(expression, because: "the registred serializer is not representation configurable"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, contains a typo... will fix in next commit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
AggregationExpression ifTrueTranslation; | ||
AggregationExpression ifFalseTranslation; | ||
IBsonSerializer resultSerializer; | ||
if (ifTrueExpression is ConstantExpression ifTrueConstantExpression) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If one result is a constant use the serializer of the other expression to serialize the constant.
@@ -57,7 +57,7 @@ public static AggregationExpression Translate(TranslationContext context, NewExp | |||
var millisecondTranslation = millisecondExpression != null ? ExpressionToAggregationExpressionTranslator.Translate(context, millisecondExpression) : null; | |||
|
|||
var ast = AstExpression.DateFromParts(yearTranslation.Ast, monthTranslation.Ast, dayTranslation.Ast, hourTranslation?.Ast, minuteTranslation?.Ast, secondTranslation?.Ast, millisecondTranslation?.Ast); | |||
var serializer = context.KnownSerializersRegistry.GetSerializer(expression); | |||
var serializer = DateTimeSerializer.Instance; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to look up anything. We know which serializer we need.
@@ -27,7 +27,7 @@ public static AstFilter Translate(TranslationContext context, ParameterExpressio | |||
{ | |||
if (context.SymbolTable.TryGetSymbol(expression, out var symbol)) | |||
{ | |||
var serializer = context.KnownSerializersRegistry.GetSerializer(expression); | |||
var serializer = symbol.Serializer; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to look up anything. We know which serializer we need.
_translationOptions = translationOptions ?? new ExpressionTranslationOptions(); | ||
_data = data; // can be null | ||
} | ||
|
||
// public properties | ||
public TranslationContextData Data => _data; | ||
public KnownSerializersRegistry KnownSerializersRegistry => _knownSerializersRegistry; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think of making the serializer registry part of the context so we're not tied to using the global registry?
In the short term it's the same thing, but it looks towards the future where we want to specify different registries for different collections.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this idea. Up to you whether you want to include it in this PR or create a separate one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to postpone doing anything like this. I don't even want to create a ticket for it until we know we need it.
By the way, I've been able to eliminate even more references to the serializer registry in the latest commit.
@@ -71,7 +72,7 @@ public void Predicate_should_use_correct_representation(int i, string projection | |||
(d => new R<E> { N = d.Id, V = d.I1 == E.E1 ? d.S1 : E.E2 }, "{ $project : { N : '$_id', V : { $cond : { if : { $eq : ['$I1', 1] }, then : '$S1', else : 'E2' } }, _id : 0 } }", new[] { E.E1, E.E2 }), | |||
(d => new R<E> { N = d.Id, V = d.I1 == E.E1 ? E.E1 : d.S2 }, "{ $project : { N : '$_id', V : { $cond : { if : { $eq : ['$I1', 1] }, then : 'E1', else : '$S2' } }, _id : 0 } }", new[] { E.E1, E.E2 }), | |||
(d => new R<E> { N = d.Id, V = d.I1 == E.E1 ? d.S1 : d.S2 }, "{ $project : { N : '$_id', V : { $cond : { if : { $eq : ['$I1', 1] }, then : '$S1', else : '$S2' } }, _id : 0 } }", new[] { E.E1, E.E2 }), | |||
(d => new R<E> { N = d.Id, V = d.S1 == E.E1 ? E.E1 : E.E2 }, "{ $project : { N : '$_id', V : { $cond : { if : { $eq : ['$S1', 'E1'] }, then : 'E1', else : 'E2' } }, _id : 0 } }", new[] { E.E1, E.E2 }), | |||
(d => new R<E> { N = d.Id, V = d.S1 == E.E1 ? Mql.Constant(E.E1, BsonType.String) : E.E2 }, "{ $project : { N : '$_id', V : { $cond : { if : { $eq : ['$S1', 'E1'] }, then : 'E1', else : 'E2' } }, _id : 0 } }", new[] { E.E1, E.E2 }), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the scenario we were discussing in Slack.
Since the ternary operator has two constants we now default to using the registered serializer, but using Mql.Constant
the user can override that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall looks great and allows us to remove a difficult-to-reason-about piece of code. One question around the TranslationContext
and the now unused serializer
parameter.
} | ||
var knownSerializersRegistry = KnownSerializerFinder.FindKnownSerializers(expression, serializer); | ||
return new TranslationContext(symbolTable, nameGenerator, knownSerializersRegistry, translationOptions, data); | ||
return new TranslationContext(symbolTable, nameGenerator, translationOptions, data); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
serializer
is never used. Are we still translating $setWindowFields
correctly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. I've removed the unused serializer paramater.
$setWindowFields
translators weren't using the KnownSerializerRegisty
so they should continue to be translated correctly.
_translationOptions = translationOptions ?? new ExpressionTranslationOptions(); | ||
_data = data; // can be null | ||
} | ||
|
||
// public properties | ||
public TranslationContextData Data => _data; | ||
public KnownSerializersRegistry KnownSerializersRegistry => _knownSerializersRegistry; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this idea. Up to you whether you want to include it in this PR or create a separate one.
src/MongoDB.Driver/Mql.cs
Outdated
/// <returns>The value.</returns> | ||
public static TValue Constant<TValue>(TValue value, IBsonSerializer<TValue> serializer) | ||
{ | ||
throw new NotSupportedException("This method is not functional. It is only usable in MongoDB LINQ queries."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haven't we moved this to helper method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes... in a different PR that was not yet pushed to main when this code was written.
I will change this now that that PR has been pushed to main.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In order to do this I'm rebasing the PR on main.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed all comments.
Probably needs at least one more careful pass before we decide it's ready to merge (for example the TODO comment).
@@ -102,7 +103,7 @@ public static AggregationExpression Translate(TranslationContext context, Method | |||
var funcLambda = ExpressionHelper.UnquoteLambdaIfQueryableMethod(method, arguments[2]); | |||
var funcParameters = funcLambda.Parameters; | |||
var accumulatorParameter = funcParameters[0]; | |||
var accumulatorSerializer = context.KnownSerializersRegistry.GetSerializer(accumulatorParameter); | |||
var accumulatorSerializer = BsonSerializer.LookupSerializer(accumulatorParameter.Type); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually the previous comment is moot. What we should be doing is using the serializer from the seedTranslation
as the serializer for the accumulator.
} | ||
else | ||
{ | ||
throw new ExpressionNotSupportedException(expression, because: "the registred serializer is not representation configurable"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
_translationOptions = translationOptions ?? new ExpressionTranslationOptions(); | ||
_data = data; // can be null | ||
} | ||
|
||
// public properties | ||
public TranslationContextData Data => _data; | ||
public KnownSerializersRegistry KnownSerializersRegistry => _knownSerializersRegistry; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to postpone doing anything like this. I don't even want to create a ticket for it until we know we need it.
By the way, I've been able to eliminate even more references to the serializer registry in the latest commit.
src/MongoDB.Driver/Mql.cs
Outdated
/// <returns>The value.</returns> | ||
public static TValue Constant<TValue>(TValue value, IBsonSerializer<TValue> serializer) | ||
{ | ||
throw new NotSupportedException("This method is not functional. It is only usable in MongoDB LINQ queries."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In order to do this I'm rebasing the PR on main.
@@ -64,7 +64,7 @@ private AstStage RenderProjectStage( | |||
out IBsonSerializer<TOutput> outputSerializer) | |||
{ | |||
var partiallyEvaluatedOutput = (Expression<Func<TGrouping, TOutput>>)PartialEvaluator.EvaluatePartially(_output); | |||
var context = TranslationContext.Create(partiallyEvaluatedOutput, inputSerializer, translationOptions); | |||
var context = TranslationContext.Create(partiallyEvaluatedOutput, translationOptions); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Create
method no longer has a serializer
parameter.
@@ -130,7 +130,7 @@ public static AggregationExpression Translate(TranslationContext context, Binary | |||
Type t when t == typeof(decimal) => DecimalSerializer.Instance, | |||
Type { IsConstructedGenericType: true } t when t.GetGenericTypeDefinition() == typeof(Nullable<>) => (IBsonSerializer)Activator.CreateInstance(typeof(NullableSerializer<>).MakeGenericType(t.GenericTypeArguments[0])), | |||
Type { IsArray: true } t => (IBsonSerializer)Activator.CreateInstance(typeof(ArraySerializer<>).MakeGenericType(t.GetElementType())), | |||
_ => context.KnownSerializersRegistry.GetSerializer(expression) // Required for Coalesce | |||
_ => BsonSerializer.LookupSerializer(expression.Type) // Required for Coalesce |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO: look into this a bit more and confirm whether a lookup is justified.
@@ -102,7 +103,7 @@ public static AggregationExpression Translate(TranslationContext context, Method | |||
var funcLambda = ExpressionHelper.UnquoteLambdaIfQueryableMethod(method, arguments[2]); | |||
var funcParameters = funcLambda.Parameters; | |||
var accumulatorParameter = funcParameters[0]; | |||
var accumulatorSerializer = context.KnownSerializersRegistry.GetSerializer(accumulatorParameter); | |||
var accumulatorSerializer = seedTranslation.Serializer; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No lookup is needed. We know which serializer to use.
@in: resultSelectorTranslation.Ast); | ||
serializer = context.KnownSerializersRegistry.GetSerializer(resultSelectorLambda); | ||
serializer = resultSelectorTranslation.Serializer; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No lookup is needed. We know which serializer to use.
var resultSelectorSymbol = context.CreateSymbol(resultSelectorParameter, resultSelectorParameterSerializer); | ||
var resultSelectorContext = context.WithSymbol(resultSelectorSymbol); | ||
var resultSelectorAccumulatorParameter = resultSelectorLambda.Parameters[0]; | ||
var resultSelectorAccumulatorSymbol = context.CreateSymbol(resultSelectorAccumulatorParameter, accumulatorSerializer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No lookup is needed. We know which serializer to use.
@@ -130,7 +130,7 @@ public static (AstFilterField, AstFilter) Translate(TranslationContext context, | |||
_ => throw new ExpressionNotSupportedException(sourceExpression, because: "OfType method is not supported with the configured discriminator convention") | |||
}; | |||
|
|||
var actualTypeSerializer = context.KnownSerializersRegistry.GetSerializer(sourceExpression); | |||
var actualTypeSerializer = BsonSerializer.LookupSerializer(actualType); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the future we might add some way to ask a serializer which serializer to use for a derived type.
For now a lookup is the only option available.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
… message wording.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM + minor suggestion
|
||
if (type.IsNullable(out var valueType) && TryGetSerializer(valueType, out var valueSerializer)) | ||
{ | ||
serializer = NullableSerializer.Create(valueSerializer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it makes sense to use singletons for Nullable<> in the same way as we have for other standard serializers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made a number of changes to this class based on discussions in Slack.
__standardSerializers.Add(typeof(uint), new UInt32Serializer(representation: BsonType.Int32, strictConverter)); | ||
__standardSerializers.Add(typeof(ulong), new UInt64Serializer(representation: BsonType.Int64, strictConverter)); | ||
|
||
var standardTypes = __standardSerializers.Keys.ToArray(); // call ToArray to make a copy of the current Keys |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if we implement local method RegisterSerializer
instead, like:
void RegisterSerializer(IBsonSerializer serializer)
{
__standardSerializers.Add(serializer.ValueType, serializer);
var nullableSerializer = NullableSerializer.Create(serializer);
__standardSerializers.Add(nullableSerializer.ValueType, nullableSerializer);
var arraySerializer = ArraySerializerHelper.CreateSerializer(serializer);
__standardSerializers.Add(arraySerializer.ValueType, nullableSerializer);
var arrayOfNullableSerializer = ArraySerializerHelper.CreateSerializer(itemSerializer: nullableSerializer);
__standardSerializers.Add(arrayOfNullableSerializer.ValueType, arrayOfNullableSerializer);
}
As far as I understood serializer.ValueType - this is the exact type that serializer will process, so we do not have to construct it here, but can relay on what serializer says.
And then simply call it for each serializer, like:
RegisterSerializer(new BooleanSerializer(representation: BsonType.Boolean));
RegisterSerializer(new ByteSerializer(representation: BsonType.Int32));
RegisterSerializer(new CharSerializer(representation: BsonType.Int32));
RegisterSerializer(new DecimalSerializer(representation: BsonType.Decimal128, allowTruncationConverter));
RegisterSerializer(new DoubleSerializer(representation: BsonType.Double, strictConverter));
RegisterSerializer(new Int16Serializer(representation: BsonType.Int32, strictConverter));
RegisterSerializer(new Int32Serializer(representation: BsonType.Int32, strictConverter));
RegisterSerializer(new Int64Serializer(representation: BsonType.Int64, strictConverter));
RegisterSerializer(new SByteSerializer(representation: BsonType.Int32));
RegisterSerializer(new SingleSerializer(representation: BsonType.Double, allowTruncationConverter));
RegisterSerializer(new UInt16Serializer(representation: BsonType.Int32, strictConverter));
RegisterSerializer(new UInt32Serializer(representation: BsonType.Int32, strictConverter));
RegisterSerializer(new UInt64Serializer(representation: BsonType.Int64, strictConverter));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like it! Done.
Only difference is I named the local method AddStandardSerializers
instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, but please check the failed tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
No description provided.