From 8eb8226829b9ea5b8d99580c5d90e4c419fc7590 Mon Sep 17 00:00:00 2001 From: rstam Date: Sat, 25 Jan 2025 13:14:07 -0800 Subject: [PATCH 1/4] CSHARP-5473: Provide API to turn LINQ expression into MQL. --- .../Linq/IMongoQueryProvider.cs | 9 +++ .../Linq3Implementation/MongoQueryProvider.cs | 10 ++++ .../Jira/CSharp5473Tests.cs | 56 +++++++++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5473Tests.cs diff --git a/src/MongoDB.Driver/Linq/IMongoQueryProvider.cs b/src/MongoDB.Driver/Linq/IMongoQueryProvider.cs index 6f26855d2be..478e89a695b 100644 --- a/src/MongoDB.Driver/Linq/IMongoQueryProvider.cs +++ b/src/MongoDB.Driver/Linq/IMongoQueryProvider.cs @@ -40,6 +40,15 @@ public interface IMongoQueryProvider : IQueryProvider /// The cancellation token. /// The value that results from executing the specified query. Task ExecuteAsync(Expression expression, CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Translates an IQueryable to MQL. + /// + /// The IQueryable. + /// The output serializer. + /// The type of the result. + /// An array of MQL pipeline stages represented as BsonDocuments. + BsonDocument[] Translate(IQueryable queryable, out IBsonSerializer outputSerializer); } /// diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/MongoQueryProvider.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/MongoQueryProvider.cs index 2717a7e71d7..770e9d614e7 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/MongoQueryProvider.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/MongoQueryProvider.cs @@ -55,6 +55,7 @@ protected MongoQueryProvider( public abstract TResult Execute(Expression expression); public abstract Task ExecuteAsync(Expression expression, CancellationToken cancellationToken); public abstract ExpressionTranslationOptions GetTranslationOptions(); + public abstract BsonDocument[] Translate(IQueryable queryable, out IBsonSerializer outputSerializer); } internal sealed class MongoQueryProvider : MongoQueryProvider @@ -152,5 +153,14 @@ public override ExpressionTranslationOptions GetTranslationOptions() var database = _database ?? _collection?.Database; return translationOptions.AddMissingOptionsFrom(database?.Client.Settings.TranslationOptions); } + + public override BsonDocument[] Translate(IQueryable queryable, out IBsonSerializer outputSerializer) + { + var translationOptions = GetTranslationOptions(); + var executableQuery = ExpressionToExecutableQueryTranslator.Translate(provider: this, queryable.Expression, translationOptions); + var stages = executableQuery.Pipeline.Ast.Stages; + outputSerializer = (IBsonSerializer)executableQuery.Pipeline.OutputSerializer; + return stages.Select(s => s.Render().AsBsonDocument).ToArray(); + } } } diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5473Tests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5473Tests.cs new file mode 100644 index 00000000000..c71fb45d87c --- /dev/null +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5473Tests.cs @@ -0,0 +1,56 @@ +/* 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 FluentAssertions; +using MongoDB.Driver.Linq; +using Xunit; + +namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira +{ + public class CSharp5473Tests : Linq3IntegrationTest + { + [Fact] + public void Select_decimal_divide_should_work() + { + var collection = GetCollection(); + + var queryable = collection.AsQueryable() + .Select(x => x.X + 1); + + var provider = (IMongoQueryProvider)queryable.Provider; + var stages = provider.Translate(queryable, out var outputSerializer); + AssertStages(stages, "{ $project : { _v : { $add : ['$X', 1] }, _id : 0 } }"); + + var result = queryable.First(); + result.Should().Be(2); + } + + private IMongoCollection GetCollection() + { + var collection = GetCollection("test"); + CreateCollection( + collection, + new C { Id = 1, X = 1 }); + return collection; + } + + private class C + { + public int Id { get; set; } + public int X { get; set; } + } + } +} From e4cc48179b65309919b31e475f8114c4ce8a6279 Mon Sep 17 00:00:00 2001 From: rstam Date: Wed, 29 Jan 2025 14:55:05 -0800 Subject: [PATCH 2/4] CSHARP-5473: Requested changes. --- .../Linq/Linq3Implementation/Jira/CSharp5473Tests.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5473Tests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5473Tests.cs index c71fb45d87c..384f3443af6 100644 --- a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5473Tests.cs +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5473Tests.cs @@ -23,7 +23,7 @@ namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira public class CSharp5473Tests : Linq3IntegrationTest { [Fact] - public void Select_decimal_divide_should_work() + public void Translate_should_work() { var collection = GetCollection(); @@ -34,7 +34,8 @@ public void Select_decimal_divide_should_work() var stages = provider.Translate(queryable, out var outputSerializer); AssertStages(stages, "{ $project : { _v : { $add : ['$X', 1] }, _id : 0 } }"); - var result = queryable.First(); + var pipeline = new BsonDocumentStagePipelineDefinition(stages, outputSerializer); + var result = collection.Aggregate(pipeline).Single(); result.Should().Be(2); } From 8ea23acef88b561f7b479c01cf2f96cbff723ae1 Mon Sep 17 00:00:00 2001 From: rstam Date: Wed, 26 Feb 2025 15:01:08 -0800 Subject: [PATCH 3/4] CSHARP-5473: Add test showing how to translate an Expression using a dummy queryable. --- .../Jira/CSharp5473Tests.cs | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5473Tests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5473Tests.cs index 384f3443af6..b38f6651cf2 100644 --- a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5473Tests.cs +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5473Tests.cs @@ -23,14 +23,34 @@ namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira public class CSharp5473Tests : Linq3IntegrationTest { [Fact] - public void Translate_should_work() + public void Translate_queryable_should_work() { var collection = GetCollection(); + var queryable = collection.AsQueryable() + .Select(x => x.X + 1); + var provider = (IMongoQueryProvider)queryable.Provider; + + var stages = provider.Translate(queryable, out var outputSerializer); + AssertStages(stages, "{ $project : { _v : { $add : ['$X', 1] }, _id : 0 } }"); + var pipeline = new BsonDocumentStagePipelineDefinition(stages, outputSerializer); + var result = collection.Aggregate(pipeline).Single(); + result.Should().Be(2); + } + + [Fact] + public void Translate_expression_should_work() + { + var collection = GetCollection(); var queryable = collection.AsQueryable() .Select(x => x.X + 1); + var expression = queryable.Expression; // collection was just used as an easy way to create the Expression - var provider = (IMongoQueryProvider)queryable.Provider; + // this is an example of how to translate an Expression using a dummyQueryable + var client = DriverTestConfiguration.Client; + var dummyDatabase = client.GetDatabase("dummy"); + var dummyQueryable = dummyDatabase.AsQueryable().Provider.CreateQuery(expression); + var provider = (IMongoQueryProvider)dummyQueryable.Provider; var stages = provider.Translate(queryable, out var outputSerializer); AssertStages(stages, "{ $project : { _v : { $add : ['$X', 1] }, _id : 0 } }"); From 2ce750850b7c21be86b718fd772385756fddd2d0 Mon Sep 17 00:00:00 2001 From: rstam Date: Thu, 3 Apr 2025 10:48:50 -0700 Subject: [PATCH 4/4] CSHARP-5473: Change Translate method to have an Expression parameter instead of an IQueryable. --- .../Linq/IMongoQueryProvider.cs | 6 ++--- .../Linq3Implementation/MongoQueryProvider.cs | 6 ++--- .../Jira/CSharp5473Tests.cs | 26 +++---------------- 3 files changed, 9 insertions(+), 29 deletions(-) diff --git a/src/MongoDB.Driver/Linq/IMongoQueryProvider.cs b/src/MongoDB.Driver/Linq/IMongoQueryProvider.cs index 478e89a695b..d51a2686eff 100644 --- a/src/MongoDB.Driver/Linq/IMongoQueryProvider.cs +++ b/src/MongoDB.Driver/Linq/IMongoQueryProvider.cs @@ -42,13 +42,13 @@ public interface IMongoQueryProvider : IQueryProvider Task ExecuteAsync(Expression expression, CancellationToken cancellationToken = default(CancellationToken)); /// - /// Translates an IQueryable to MQL. + /// Translates an Expression to MQL. /// - /// The IQueryable. + /// The expression. /// The output serializer. /// The type of the result. /// An array of MQL pipeline stages represented as BsonDocuments. - BsonDocument[] Translate(IQueryable queryable, out IBsonSerializer outputSerializer); + BsonDocument[] Translate(Expression expression, out IBsonSerializer outputSerializer); } /// diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/MongoQueryProvider.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/MongoQueryProvider.cs index 770e9d614e7..e411b2ac0c5 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/MongoQueryProvider.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/MongoQueryProvider.cs @@ -55,7 +55,7 @@ protected MongoQueryProvider( public abstract TResult Execute(Expression expression); public abstract Task ExecuteAsync(Expression expression, CancellationToken cancellationToken); public abstract ExpressionTranslationOptions GetTranslationOptions(); - public abstract BsonDocument[] Translate(IQueryable queryable, out IBsonSerializer outputSerializer); + public abstract BsonDocument[] Translate(Expression expression, out IBsonSerializer outputSerializer); } internal sealed class MongoQueryProvider : MongoQueryProvider @@ -154,10 +154,10 @@ public override ExpressionTranslationOptions GetTranslationOptions() return translationOptions.AddMissingOptionsFrom(database?.Client.Settings.TranslationOptions); } - public override BsonDocument[] Translate(IQueryable queryable, out IBsonSerializer outputSerializer) + public override BsonDocument[] Translate(Expression expression, out IBsonSerializer outputSerializer) { var translationOptions = GetTranslationOptions(); - var executableQuery = ExpressionToExecutableQueryTranslator.Translate(provider: this, queryable.Expression, translationOptions); + var executableQuery = ExpressionToExecutableQueryTranslator.Translate(provider: this, expression, translationOptions); var stages = executableQuery.Pipeline.Ast.Stages; outputSerializer = (IBsonSerializer)executableQuery.Pipeline.OutputSerializer; return stages.Select(s => s.Render().AsBsonDocument).ToArray(); diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5473Tests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5473Tests.cs index b38f6651cf2..8cf20decca5 100644 --- a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5473Tests.cs +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5473Tests.cs @@ -22,36 +22,16 @@ namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira { public class CSharp5473Tests : Linq3IntegrationTest { - [Fact] - public void Translate_queryable_should_work() - { - var collection = GetCollection(); - var queryable = collection.AsQueryable() - .Select(x => x.X + 1); - var provider = (IMongoQueryProvider)queryable.Provider; - - var stages = provider.Translate(queryable, out var outputSerializer); - AssertStages(stages, "{ $project : { _v : { $add : ['$X', 1] }, _id : 0 } }"); - - var pipeline = new BsonDocumentStagePipelineDefinition(stages, outputSerializer); - var result = collection.Aggregate(pipeline).Single(); - result.Should().Be(2); - } - [Fact] public void Translate_expression_should_work() { var collection = GetCollection(); var queryable = collection.AsQueryable() .Select(x => x.X + 1); - var expression = queryable.Expression; // collection was just used as an easy way to create the Expression + var expression = queryable.Expression; // queryable was just used as an easy way to create the expression and the provider + var provider = (IMongoQueryProvider)queryable.Provider; - // this is an example of how to translate an Expression using a dummyQueryable - var client = DriverTestConfiguration.Client; - var dummyDatabase = client.GetDatabase("dummy"); - var dummyQueryable = dummyDatabase.AsQueryable().Provider.CreateQuery(expression); - var provider = (IMongoQueryProvider)dummyQueryable.Provider; - var stages = provider.Translate(queryable, out var outputSerializer); + var stages = provider.Translate(expression, out var outputSerializer); AssertStages(stages, "{ $project : { _v : { $add : ['$X', 1] }, _id : 0 } }"); var pipeline = new BsonDocumentStagePipelineDefinition(stages, outputSerializer);