From fcbc397b63783357d5f10e63b9a29024451d4d23 Mon Sep 17 00:00:00 2001 From: rstam Date: Tue, 6 May 2025 12:00:39 -0700 Subject: [PATCH] CSHARP-3435: FilterDefinition Inject method should use root serializer --- .../InjectMethodToFilterTranslator.cs | 17 ++- .../Jira/CSharp3435Tests.cs | 112 ++++++++++++++++++ 2 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp3435Tests.cs diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/InjectMethodToFilterTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/InjectMethodToFilterTranslator.cs index 61493819b50..a21ed9d567e 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/InjectMethodToFilterTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/InjectMethodToFilterTranslator.cs @@ -13,10 +13,12 @@ * limitations under the License. */ +using System.Linq; using System.Linq.Expressions; using System.Reflection; using MongoDB.Bson; using MongoDB.Bson.Serialization; +using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions; using MongoDB.Driver.Linq.Linq3Implementation.Ast.Filters; using MongoDB.Driver.Linq.Linq3Implementation.ExtensionMethods; using MongoDB.Driver.Linq.Linq3Implementation.Misc; @@ -44,12 +46,21 @@ public static AstFilter Translate(TranslationContext context, MethodCallExpressi var filterExpression = arguments[0]; var filterDefinition = filterExpression.GetConstantValue(expression); var filterDefinitionType = filterDefinition.GetType(); // we KNOW it's a FilterDefinition because of the Inject method signature - var documentType = filterDefinitionType.GetGenericArguments()[0]; + var filterDefinitionDocumentType = filterDefinitionType.GetGenericArguments()[0]; + var rootSymbol = context.SymbolTable.Symbols.SingleOrDefault(s => s.Ast.IsRootVar()); + if (rootSymbol == null) + { + throw new ExpressionNotSupportedException(expression, because: "there is no current root symbol"); + } + var documentSerializer = rootSymbol.Serializer; + if (filterDefinitionDocumentType != documentSerializer.ValueType) + { + throw new ExpressionNotSupportedException(expression, because: $"FilterDefinition TDocument type: {filterDefinitionDocumentType} does not match document type {documentSerializer.ValueType} "); + } var serializerRegistry = BsonSerializer.SerializerRegistry; - var documentSerializer = serializerRegistry.GetSerializer(documentType); // TODO: is this the right serializer? - var renderFilterMethod = __renderFilterMethodInfo.MakeGenericMethod(documentType); + var renderFilterMethod = __renderFilterMethodInfo.MakeGenericMethod(filterDefinitionDocumentType); var renderedFilter = (BsonDocument)renderFilterMethod.Invoke(null, new[] { filterDefinition, documentSerializer, serializerRegistry, context.TranslationOptions }); return AstFilter.Raw(renderedFilter); diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp3435Tests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp3435Tests.cs new file mode 100644 index 00000000000..33584f1b44e --- /dev/null +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp3435Tests.cs @@ -0,0 +1,112 @@ +/* Copyright 2010-present MongoDB Inc. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +using System.Linq; +using MongoDB.Driver.Linq; +using Xunit; + +namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira; + +public class CSharp3435Tests : LinqIntegrationTest +{ + public CSharp3435Tests(ClassFixture fixture) + : base(fixture) + { + } + + [Fact] + public void Where_should_work() + { + var queryable = CreateQueryable() + .Where(x => x.NormalizedUsername == "PAPLABROS"); + + var stages = Translate(Fixture.UserClaimCollection, queryable); + AssertStages( + stages, + "{ $project : { _outer : '$$ROOT', _id : 0 } }", + "{ $lookup : { from : 'Users', localField : '_outer.UserId', foreignField : '_id', as : '_inner' } }", + "{ $project : { claim : '$_outer', users : '$_inner', _id : 0 } }", + "{ $match : { 'claim.ClaimType' : 'Moderator' } }", + "{ $project : { _v : { $arrayElemAt : ['$users', 0] }, _id : 0 } }", + "{ $match : { '_v.NormalizedUsername' : 'PAPLABROS' } }"); + } + + [Fact] + public void Where_with_Inject_should_work() + { + var filter = Builders.Filter.Eq(x => x.NormalizedUsername, "PAPLABROS"); + var queryable = CreateQueryable() + .Where(x => filter.Inject()); + + var stages = Translate(Fixture.UserClaimCollection, queryable); + AssertStages( + stages, + "{ $project : { _outer : '$$ROOT', _id : 0 } }", + "{ $lookup : { from : 'Users', localField : '_outer.UserId', foreignField : '_id', as : '_inner' } }", + "{ $project : { claim : '$_outer', users : '$_inner', _id : 0 } }", + "{ $match : { 'claim.ClaimType' : 'Moderator' } }", + "{ $project : { _v : { $arrayElemAt : ['$users', 0] }, _id : 0 } }", + "{ $match : { '_v.NormalizedUsername' : 'PAPLABROS' } }"); + } + + public IQueryable CreateQueryable() + { + var usersCollection = Fixture.UserCollection; + var userClaimsCollection = Fixture.UserClaimCollection; + + var queryable = + from claim in userClaimsCollection.AsQueryable() + join user in usersCollection.AsQueryable() on claim.UserId equals user.Id into users + where claim.ClaimType == "Moderator" + select users.First(); + + // this is the equivalent method syntax + // var queryable = userClaimsCollection.AsQueryable() + // .GroupJoin( + // usersCollection.AsQueryable(), + // claim => claim.UserId, + // user => user.Id, + // (claim, users) => new { claim, users }) + // .Where(x => x.claim.ClaimType == "Moderator") + // .Select(x => x.users.First()); + + return queryable; + } + + public class User + { + public int Id { get; set; } + public string NormalizedUsername { get; set; } + } + + public class UserClaim + { + public int Id { get; set; } + public int UserId { get; set; } + public string ClaimType { get; set; } + } + + public sealed class ClassFixture : MongoDatabaseFixture + { + public IMongoCollection UserCollection { get; private set; } + public IMongoCollection UserClaimCollection { get; private set; } + + protected override void InitializeFixture() + { + UserCollection = CreateCollection("Users"); + UserClaimCollection = CreateCollection("UserClaims"); + } + } +}