diff --git a/CSharpDriver.Bson.slnf b/CSharpDriver.Bson.slnf deleted file mode 100644 index 56265b22f69..00000000000 --- a/CSharpDriver.Bson.slnf +++ /dev/null @@ -1,10 +0,0 @@ -{ - "solution": { - "path": "CSharpDriver.sln", - "projects": [ - "src\\MongoDB.Bson\\MongoDB.Bson.csproj", - "tests\\MongoDB.Bson.TestHelpers\\MongoDB.Bson.TestHelpers.csproj", - "tests\\MongoDB.Bson.Tests\\MongoDB.Bson.Tests.csproj" - ] - } -} diff --git a/CSharpDriver.Driver.Core.slnf b/CSharpDriver.Driver.Core.slnf deleted file mode 100644 index 8ad13e9d183..00000000000 --- a/CSharpDriver.Driver.Core.slnf +++ /dev/null @@ -1,12 +0,0 @@ -{ - "solution": { - "path": "CSharpDriver.sln", - "projects": [ - "src\\MongoDB.Bson\\MongoDB.Bson.csproj", - "src\\MongoDB.Driver.Core\\MongoDB.Driver.Core.csproj", - "tests\\MongoDB.Bson.TestHelpers\\MongoDB.Bson.TestHelpers.csproj", - "tests\\MongoDB.Driver.Core.TestHelpers\\MongoDB.Driver.Core.TestHelpers.csproj", - "tests\\MongoDB.Driver.Core.Tests\\MongoDB.Driver.Core.Tests.csproj" - ] - } -} diff --git a/CSharpDriver.Driver.slnf b/CSharpDriver.Driver.slnf deleted file mode 100644 index 59d218d11f0..00000000000 --- a/CSharpDriver.Driver.slnf +++ /dev/null @@ -1,15 +0,0 @@ -{ - "solution": { - "path": "CSharpDriver.sln", - "projects": [ - "src\\MongoDB.Bson\\MongoDB.Bson.csproj", - "src\\MongoDB.Driver.Core\\MongoDB.Driver.Core.csproj", - "src\\MongoDB.Driver.GridFS\\MongoDB.Driver.GridFS.csproj", - "src\\MongoDB.Driver\\MongoDB.Driver.csproj", - "tests\\MongoDB.Bson.TestHelpers\\MongoDB.Bson.TestHelpers.csproj", - "tests\\MongoDB.Driver.Core.TestHelpers\\MongoDB.Driver.Core.TestHelpers.csproj", - "tests\\MongoDB.Driver.TestHelpers\\MongoDB.Driver.TestHelpers.csproj", - "tests\\MongoDB.Driver.Tests\\MongoDB.Driver.Tests.csproj" - ] - } -} diff --git a/README.md b/README.md index 16be4f4970e..d3f37f5d2bf 100644 --- a/README.md +++ b/README.md @@ -76,96 +76,4 @@ Contributing Please see our [guidelines](CONTRIBUTING.md) for contributing to the driver. -### Maintainers: -* Boris Dogadov boris.dogadov@mongodb.com -* James Kovacs james.kovacs@mongodb.com -* Oleksandr Poliakov oleksandr.poliakov@mongodb.com -* Robert Stam robert@mongodb.com - -### Contributors: -* Alexander Aramov https://github.com/alex687 -* Bar Arnon https://github.com/I3arnon -* Wan Bachtiar https://github.com/sindbach -* Mark Benvenuto https://github.com/markbenvenuto -* Brian Buvinghausen https://github.com/buvinghausen -* Bit Diffusion Limited code@bitdiff.com -* Jimmy Bogard https://github.com/jbogard -* Ross Buggins https://github.com/rbugginsvia -* Nima Boscarino https://github.com/NimaBoscarino -* Oscar Bralo https://github.com/Oscarbralo -* Alex Brown https://github.com/alexjamesbrown -* Ethan Celletti https://github.com/Gekctek -* Chris Cho https://github.com/ccho-mongodb -* Adam Avery Cole https://github.com/adamaverycole -* Nate Contino https://github.com/nathan-contino-mongo -* Alex Dawes https://github.com/alexdawes -* Justin Dearing zippy1981@gmail.com -* Dan DeBilt dan.debilt@gmail.com -* Teun Duynstee teun@duynstee.com -* Einar Egilsson https://github.com/einaregilsson -* Ken Egozi mail@kenegozi.com -* Alexander Endris https://github.com/AlexEndris -* Daniel Goldman daniel@stackwave.com -* David Golub https://github.com/dgolub -* Simon Green simon@captaincodeman.com -* Bouke Haarsma https://github.com/Bouke -* James Hadwen james.hadwen@sociustec.com -* Nuri Halperin https://github.com/nurih -* Daniel Hegener daniel.hegener@fisglobal.com -* Nikola Irinchev https://github.com/nirinchev -* Jacob Jewell jacobjewell@eflexsystems.com -* Vincent Kam https://github.com/vincentkam -* Danny Kendrick https://github.com/dkendrick -* Ruslan Khasanbaev https://github.com/flaksirus -* Konstantin Khitrykh https://github.com/KonH -* Brian Knight brianknight10@gmail.com -* John Knoop https://github.com/johnknoop -* Andrey Kondratyev https://github.com/byTimo -* Anatoly Koperin https://github.com/ExM -* Nik Kolev nkolev@gmail.com -* Oleg Kosmakov https://github.com/kosmakoff -* Maksim Krautsou https://github.com/MaKCbIMKo -* Richard Kreuter richard@10gen.com -* Daniel Lee https://github.com/dlee148 -* Ming Yau Lee https://github.com/mingyaulee -* Kevin Lewis kevin.l.lewis@gmail.com -* Dow Liu redforks@gmail.com -* Chuck Lu https://github.com/chucklu -* Alex Lyman mail.alex.lyman@gmail.com -* Tomasz Masternak https://github.com/tmasternak -* Mikalai Mazurenka mikalai.mazurenka@mongodb.com -* John Murphy https://github.com/jsmurphy -* Alexander Nagy optimiz3@gmail.com -* Sridhar Nanjundeswaran https://github.com/sridharn -* Nathan https://github.com/terakilobyte -* Adelin Owona https://github.com/adelinowona -* Rachelle Palmer https://github.com/techbelle -* Rich Quackenbush rich.quackenbush@captiveaire.com -* Carl Reinke https://github.com/mindless2112 -* Rodrigo Reis https://github.com/rodrigoreis -* Gian Maria Ricci https://github.com/alkampfergit -* Andrew Rondeau github@andrewrondeau.com -* Ed Rooth edward.rooth@wallstreetjapan.com -* Katie Sadoff https://github.com/ksadoff -* Manas Sahu https://github.com/Zangetsu112 -* Sam558 https://github.com/Sam558 -* Vladimir Setyaev setyaev_v@pgstudio.io -* Sergey Shushlyapin https://github.com/sergeyshushlyapin -* Alexey Skalozub pieceofsummer@gmail.com -* Kevin Smith https://github.com/kevbite -* Pete Smith roysvork@gmail.com -* Matteo Spreafico https://github.com/MatteoSp -* staywellandy https://github.com/staywellandy -* Vyacheslav Stroy https://github.com/kreig -* Jake Sta. Teresa https://github.com/JakeStaTeresa -* Testo test1@doramail.com -* TimTim https://github.com/wegylexy -* Craig Wilson https://github.com/craiggwilson -* Zhmayev Yaroslav https://github.com/salaros -* Aristarkh Zagorodnikov https://github.com/onyxmaster -* Samir Boulema https://github.com/sboulema -* Dmitry Lukyanov https://github.com/DmitryLukyanov -* Andrea Balducci https://github.com/andreabalducci -* Sergei Lipin https://github.com/prchaoz - -If you have contributed and we have neglected to add you to this list please contact one of the maintainers to be added to the list (with apologies). +Thank you to [everyone](https://github.com/mongodb/mongo-csharp-driver/graphs/contributors) who has contributed to this project. diff --git a/Release Notes/Release Notes v2.25.0.md b/Release Notes/Release Notes v2.25.0.md index ddc3b28ef61..d172ba58fe4 100644 --- a/Release Notes/Release Notes v2.25.0.md +++ b/Release Notes/Release Notes v2.25.0.md @@ -17,4 +17,4 @@ The main new features in 2.25.0 include: The full list of issues resolved in this release is available at [CSHARP JIRA project](https://jira.mongodb.org/issues/?jql=project%20%3D%20CSHARP%20AND%20fixVersion%20%3D%202.25.0%20ORDER%20BY%20key%20ASC). -Documentation on the .NET driver can be found [here](https://www.mongodb.com/docs/drivers/csharp/v2.25.0}/). +Documentation on the .NET driver can be found [here](https://www.mongodb.com/docs/drivers/csharp/v2.25/). diff --git a/Release Notes/Release Notes v2.26.0.md b/Release Notes/Release Notes v2.26.0.md index c48d4d466c6..469b6874846 100644 --- a/Release Notes/Release Notes v2.26.0.md +++ b/Release Notes/Release Notes v2.26.0.md @@ -16,4 +16,4 @@ The main new features in 2.26.0 include: The full list of issues resolved in this release is available at [CSHARP JIRA project](https://jira.mongodb.org/issues/?jql=project%20%3D%20CSHARP%20AND%20fixVersion%20%3D%202.26.0%20ORDER%20BY%20key%20ASC). -Documentation on the .NET driver can be found [here](https://www.mongodb.com/docs/drivers/csharp/v2.26.0}/). +Documentation on the .NET driver can be found [here](https://www.mongodb.com/docs/drivers/csharp/v2.26/). diff --git a/Release Notes/Release Notes v2.28.0.md b/Release Notes/Release Notes v2.28.0.md new file mode 100644 index 00000000000..635709c65e3 --- /dev/null +++ b/Release Notes/Release Notes v2.28.0.md @@ -0,0 +1,22 @@ +# .NET Driver Version 2.28.0 Release Notes + +This is the general availability release for the 2.28.0 version of the driver. + +NOTICE: MongoDB 3.6 reached end-of-life in April 2021. The .NET/C# Driver will be removing support for MongoDB 3.6 in an upcoming release. + +The main new features in 2.28.0 include: + ++ Provide Strong-Named Assemblies - [CSHARP-1276](https://jira.mongodb.org/browse/CSHARP-1276) ++ Support additional numeric conversions involving Nullable<T> - [CSHARP-5180](https://jira.mongodb.org/browse/CSHARP-5180) ++ CSFLE/QE KMIP support "delegated" protocol - [CSHARP-4941](https://jira.mongodb.org/browse/CSHARP-4941) + +## Bug fixes: ++ Verify that operands to numeric operators in LINQ expressions are represented as numbers on the server - [CSHARP-4985](https://jira.mongodb.org/browse/CSHARP-4985) ++ IReadOnlyDictionary indexer access fails to translate in v3 - [CSHARP-5171](https://jira.mongodb.org/browse/CSHARP-5171) ++ Projection Expressions Fail to Deserialize Data Correctly - [CSHARP-5162](https://jira.mongodb.org/browse/CSHARP-5162) ++ Enum conversion within IQueryable fails with Expression not supported exception - [CSHARP-5043](https://jira.mongodb.org/browse/CSHARP-5043) ++ IMongoCollection.AsQueryable().Select() fails for array type (regression) - [CSHARP-4957](https://jira.mongodb.org/browse/CSHARP-4957) + +The full list of issues resolved in this release is available at [CSHARP JIRA project](https://jira.mongodb.org/issues/?jql=project%20%3D%20CSHARP%20AND%20fixVersion%20%3D%202.28.0%20ORDER%20BY%20key%20ASC). + +Documentation on the .NET driver can be found [here](https://www.mongodb.com/docs/drivers/csharp/v2.28/). diff --git a/packageIcon.png b/packageIcon.png index d5dfcc632b4..e55bc6a1c91 100644 Binary files a/packageIcon.png and b/packageIcon.png differ diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 59caaf35767..f1b34632505 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -16,6 +16,8 @@ <Copyright>Copyright © 2010-present MongoDB Inc.</Copyright> <Authors>Etherna Sagl</Authors> <PackageIcon>packageIcon.png</PackageIcon> + <PackageReadmeFile>README.md</PackageReadmeFile> + <PackageReleaseNotes>https://github.com/mongodb/mongo-csharp-driver/releases/tag/v$(Version)</PackageReleaseNotes> <PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance> <PackageProjectUrl>https://www.mongodb.com/docs/drivers/csharp/</PackageProjectUrl> <PackageTags>mongodb;mongo;nosql</PackageTags> @@ -50,5 +52,6 @@ <ItemGroup> <None Include="..\..\packageIcon.png" Pack="true" PackagePath="" /> + <None Include="..\..\README.md" Pack="true" PackagePath=""/> </ItemGroup> </Project> diff --git a/src/MongoDB.Bson/Properties/AssemblyInfo.cs b/src/MongoDB.Bson/Properties/AssemblyInfo.cs index 37a86d96563..2e340e738cb 100644 --- a/src/MongoDB.Bson/Properties/AssemblyInfo.cs +++ b/src/MongoDB.Bson/Properties/AssemblyInfo.cs @@ -26,4 +26,5 @@ // as Xamarin.iOS/Xamarin.Mac. [assembly: Preserve(AllMembers = true)] -[assembly: InternalsVisibleTo("MongoDB.Bson.Tests")] +[assembly: InternalsVisibleTo("MongoDB.Bson.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010035287f0d3883c0a075c88e0cda3ce93b621003ecbd5e920d4a8c7238564f4d2f4f68116aca28c9b21341dc3a877679c14556192b2b2f5fe2c11d624e0894d308ff7b94bf6fd72aef1b41017ffe2572e99019d1c61963e68cd0ed67734a42cb333b808e3867cbe631937214e32e409fb1fa62fdb69d494c2530e64a40e417d6ee")] +[assembly: InternalsVisibleTo("MongoDB.Analyzer.MQLGenerator, PublicKey=002400000480000094000000060200000024000052534131000400000100010035287f0d3883c0a075c88e0cda3ce93b621003ecbd5e920d4a8c7238564f4d2f4f68116aca28c9b21341dc3a877679c14556192b2b2f5fe2c11d624e0894d308ff7b94bf6fd72aef1b41017ffe2572e99019d1c61963e68cd0ed67734a42cb333b808e3867cbe631937214e32e409fb1fa62fdb69d494c2530e64a40e417d6ee")] diff --git a/src/MongoDB.Bson/Serialization/TypeNameDiscriminator.cs b/src/MongoDB.Bson/Serialization/TypeNameDiscriminator.cs index 9455ece825d..5527778128f 100644 --- a/src/MongoDB.Bson/Serialization/TypeNameDiscriminator.cs +++ b/src/MongoDB.Bson/Serialization/TypeNameDiscriminator.cs @@ -145,7 +145,9 @@ public static string GetDiscriminator(Type type) if (match.Success) { var publicKeyToken = match.Groups["token"].Value; - if (publicKeyToken == "null") + if (publicKeyToken == "null" || + // MongoDB's assemblies should use "type name, assembly name" discriminator format for backward compatibility + (assembly.FullName.StartsWith("MongoDB") && publicKeyToken == "94992a530f44e321")) { var dllName = match.Groups["dll"].Value; assemblyName = dllName; diff --git a/src/MongoDB.Driver.Core/Core/Clusters/ServerSelectors/WritableServerSelector.cs b/src/MongoDB.Driver.Core/Core/Clusters/ServerSelectors/WritableServerSelector.cs index c6cfef1e58c..6beba601631 100644 --- a/src/MongoDB.Driver.Core/Core/Clusters/ServerSelectors/WritableServerSelector.cs +++ b/src/MongoDB.Driver.Core/Core/Clusters/ServerSelectors/WritableServerSelector.cs @@ -98,7 +98,7 @@ public override string ToString() private bool CanUseSecondaries(ClusterDescription cluster, List<ServerDescription> servers) { - if (_mayUseSecondary?.ReadPreference == null || servers.Count == 0) + if (_mayUseSecondary?.ReadPreference == null) { return false; } @@ -107,6 +107,11 @@ private bool CanUseSecondaries(ClusterDescription cluster, List<ServerDescriptio { case ClusterType.ReplicaSet: case ClusterType.Sharded: + if (servers.Count == 0) + { + return true; + } + return servers.All(s => _mayUseSecondary.CanUseSecondary(s)); case ClusterType.LoadBalanced: diff --git a/src/MongoDB.Driver.Core/Core/Servers/ServerMonitor.cs b/src/MongoDB.Driver.Core/Core/Servers/ServerMonitor.cs index ecd92457783..35f876990a4 100644 --- a/src/MongoDB.Driver.Core/Core/Servers/ServerMonitor.cs +++ b/src/MongoDB.Driver.Core/Core/Servers/ServerMonitor.cs @@ -369,11 +369,8 @@ private void Heartbeat(CancellationToken cancellationToken) ServerDescription newDescription; if (heartbeatHelloResult != null) { - var averageRoundTripTime = _roundTripTimeMonitor.Average; - var averageRoundTripTimeRounded = TimeSpan.FromMilliseconds(Math.Round(averageRoundTripTime.TotalMilliseconds)); - newDescription = _baseDescription.With( - averageRoundTripTime: averageRoundTripTimeRounded, + averageRoundTripTime: _roundTripTimeMonitor.Average, canonicalEndPoint: heartbeatHelloResult.Me, electionId: heartbeatHelloResult.ElectionId, helloOk: heartbeatHelloResult.HelloOk, diff --git a/src/MongoDB.Driver.Core/Etherna.MongoDB.Driver.Core.csproj b/src/MongoDB.Driver.Core/Etherna.MongoDB.Driver.Core.csproj index ea71cbecf12..c205b5f68db 100644 --- a/src/MongoDB.Driver.Core/Etherna.MongoDB.Driver.Core.csproj +++ b/src/MongoDB.Driver.Core/Etherna.MongoDB.Driver.Core.csproj @@ -28,7 +28,7 @@ <PackageReference Include="AWSSDK.SecurityToken" Version="3.7.100.14" /> <PackageReference Include="DnsClient" Version="1.6.1" /> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.0" /> - <PackageReference Include="MongoDB.Libmongocrypt" Version="1.10.0" /> + <PackageReference Include="MongoDB.Libmongocrypt" Version="1.11.0" /> <PackageReference Include="SharpCompress" Version="0.30.1" /> <PackageReference Include="Snappier" Version="1.0.0" /> <PackageReference Include="ZstdSharp.Port" Version="0.7.3" /> diff --git a/src/MongoDB.Driver.Core/Properties/AssemblyInfo.cs b/src/MongoDB.Driver.Core/Properties/AssemblyInfo.cs index 513e5e05683..04f0330d93e 100644 --- a/src/MongoDB.Driver.Core/Properties/AssemblyInfo.cs +++ b/src/MongoDB.Driver.Core/Properties/AssemblyInfo.cs @@ -28,10 +28,10 @@ [assembly: InternalsVisibleTo("Etherna.MongoDB.Driver")] [assembly: InternalsVisibleTo("Etherna.MongoDB.Driver.TestHelpers")] [assembly: InternalsVisibleTo("Etherna.MongoDB.Driver.Legacy")] -[assembly: InternalsVisibleTo("Etherna.MongoDB.Driver.Core.FunctionalTests")] [assembly: InternalsVisibleTo("Etherna.MongoDB.Driver.Core.TestHelpers")] [assembly: InternalsVisibleTo("Etherna.MongoDB.Driver.Core.Tests")] [assembly: InternalsVisibleTo("Etherna.MongoDB.Driver.Legacy.Tests")] [assembly: InternalsVisibleTo("Etherna.MongoDB.Driver.Legacy.TestHelpers")] [assembly: InternalsVisibleTo("Etherna.MongoDB.Driver.Tests")] -[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] +[assembly: InternalsVisibleTo("MongoDB.Analyzer.MQLGenerator, PublicKey=002400000480000094000000060200000024000052534131000400000100010035287f0d3883c0a075c88e0cda3ce93b621003ecbd5e920d4a8c7238564f4d2f4f68116aca28c9b21341dc3a877679c14556192b2b2f5fe2c11d624e0894d308ff7b94bf6fd72aef1b41017ffe2572e99019d1c61963e68cd0ed67734a42cb333b808e3867cbe631937214e32e409fb1fa62fdb69d494c2530e64a40e417d6ee")] diff --git a/src/MongoDB.Driver.GridFS/Properties/AssemblyInfo.cs b/src/MongoDB.Driver.GridFS/Properties/AssemblyInfo.cs index d19d1222e2f..45a878a8830 100644 --- a/src/MongoDB.Driver.GridFS/Properties/AssemblyInfo.cs +++ b/src/MongoDB.Driver.GridFS/Properties/AssemblyInfo.cs @@ -25,4 +25,4 @@ // Required for most of the reflection usage in Xamarin.iOS/Xamarin.Mac. [assembly: Preserve(AllMembers = true)] -[assembly: InternalsVisibleTo("MongoDB.Driver.GridFS.Tests")] +[assembly: InternalsVisibleTo("MongoDB.Driver.GridFS.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010035287f0d3883c0a075c88e0cda3ce93b621003ecbd5e920d4a8c7238564f4d2f4f68116aca28c9b21341dc3a877679c14556192b2b2f5fe2c11d624e0894d308ff7b94bf6fd72aef1b41017ffe2572e99019d1c61963e68cd0ed67734a42cb333b808e3867cbe631937214e32e409fb1fa62fdb69d494c2530e64a40e417d6ee")] diff --git a/src/MongoDB.Driver/Etherna.MongoDB.Driver.csproj b/src/MongoDB.Driver/Etherna.MongoDB.Driver.csproj index a59f3a599ec..46766e57565 100644 --- a/src/MongoDB.Driver/Etherna.MongoDB.Driver.csproj +++ b/src/MongoDB.Driver/Etherna.MongoDB.Driver.csproj @@ -19,7 +19,7 @@ <ItemGroup> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.0" /> - <PackageReference Include="MongoDB.Libmongocrypt" Version="1.10.0" /> + <PackageReference Include="MongoDB.Libmongocrypt" Version="1.11.0" /> </ItemGroup> <ItemGroup> diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Optimizers/AstSimplifier.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Optimizers/AstSimplifier.cs index 3614c06f848..48fb600e90f 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Optimizers/AstSimplifier.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Optimizers/AstSimplifier.cs @@ -16,7 +16,6 @@ using Etherna.MongoDB.Bson; using Etherna.MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions; using Etherna.MongoDB.Driver.Linq.Linq3Implementation.Ast.Filters; -using Etherna.MongoDB.Driver.Linq.Linq3Implementation.Ast.Stages; using Etherna.MongoDB.Driver.Linq.Linq3Implementation.Ast.Visitors; namespace Etherna.MongoDB.Driver.Linq.Linq3Implementation.Ast.Optimizers @@ -37,6 +36,48 @@ public static TNode SimplifyAndConvert<TNode>(TNode node) } #endregion + public override AstNode VisitCondExpression(AstCondExpression node) + { + // { $cond : [{ $eq : [expr1, null] }, null, expr2] } + if (node.If is AstBinaryExpression binaryIfpression && + binaryIfpression.Operator == AstBinaryOperator.Eq && + binaryIfpression.Arg1 is AstExpression expr1 && + binaryIfpression.Arg2 is AstConstantExpression constantComparandExpression && + constantComparandExpression.Value == BsonNull.Value && + node.Then is AstConstantExpression constantThenExpression && + constantThenExpression.Value == BsonNull.Value && + node.Else is AstExpression expr2) + { + // { $cond : [{ $eq : [expr, null] }, null, expr] } => expr + if (expr1 == expr2) + { + return Visit(expr2); + } + + // { $cond : [{ $eq : [expr, null] }, null, { $toT : expr }] } => { $toT : expr } for operators that map null to null + if (expr2 is AstUnaryExpression unaryElseExpression && + OperatorMapsNullToNull(unaryElseExpression.Operator) && + unaryElseExpression.Arg == expr1) + { + return Visit(expr2); + } + } + + return base.VisitCondExpression(node); + + static bool OperatorMapsNullToNull(AstUnaryOperator @operator) + { + return @operator switch + { + AstUnaryOperator.ToDecimal => true, + AstUnaryOperator.ToDouble => true, + AstUnaryOperator.ToInt => true, + AstUnaryOperator.ToLong => true, + _ => false + }; + } + } + public override AstNode VisitFieldOperationFilter(AstFieldOperationFilter node) { node = (AstFieldOperationFilter)base.VisitFieldOperationFilter(node); @@ -281,6 +322,22 @@ bool TrySimplifyAsLet(AstGetFieldExpression node, out AstExpression simplified) } } + public override AstNode VisitLetExpression(AstLetExpression node) + { + node = (AstLetExpression)base.VisitLetExpression(node); + + // { $let : { vars : { var : expr }, in : "$$var" } } => expr + if (node.Vars.Count == 1 && + node.Vars[0].Var.Name is string varName && + node.In is AstVarExpression varExpression && + varExpression.Name == varName) + { + return node.Vars[0].Value; + } + + return node; + } + public override AstNode VisitMapExpression(AstMapExpression node) { // { $map : { input : <input>, as : "v", in : "$$v.x" } } => { $getField : { field : "x", input : <input> } } diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/SerializationHelper.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/SerializationHelper.cs index a39ee290aec..dcc6a90a72a 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/SerializationHelper.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/SerializationHelper.cs @@ -21,6 +21,7 @@ using Etherna.MongoDB.Bson.Serialization.Options; using Etherna.MongoDB.Bson.Serialization.Serializers; using Etherna.MongoDB.Driver.Linq.Linq3Implementation.Serializers; +using Etherna.MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators; namespace Etherna.MongoDB.Driver.Linq.Linq3Implementation.Misc { @@ -35,6 +36,11 @@ public static void EnsureRepresentationIsArray(Expression expression, IBsonSeria } } + public static void EnsureRepresentationIsNumeric(Expression expression, AggregationExpression translation) + { + EnsureRepresentationIsNumeric(expression, translation.Serializer); + } + public static void EnsureRepresentationIsNumeric(Expression expression, IBsonSerializer serializer) { var representation = GetRepresentation(serializer); @@ -56,6 +62,11 @@ public static BsonType GetRepresentation(IBsonSerializer serializer) return GetRepresentation(downcastingSerializer.DerivedSerializer); } + if (serializer is IEnumUnderlyingTypeSerializer enumUnderlyingTypeSerializer) + { + return GetRepresentation(enumUnderlyingTypeSerializer.EnumSerializer); + } + if (serializer is IImpliedImplementationInterfaceSerializer impliedImplementationSerializer) { return GetRepresentation(impliedImplementationSerializer.ImplementationSerializer); @@ -82,6 +93,11 @@ public static BsonType GetRepresentation(IBsonSerializer serializer) return keyValuePairSerializer.Representation; } + if (serializer is INullableSerializer nullableSerializer) + { + return GetRepresentation(nullableSerializer.ValueSerializer); + } + // for backward compatibility assume that any remaining implementers of IBsonDocumentSerializer are represented as documents if (serializer is IBsonDocumentSerializer) { @@ -97,6 +113,15 @@ public static BsonType GetRepresentation(IBsonSerializer serializer) return BsonType.Undefined; } + public static bool IsIntegerRepresentation(BsonType representation) + { + return representation switch + { + BsonType.Int32 or BsonType.Int64 => true, + _ => false + }; + } + public static bool IsNumericRepresentation(BsonType representation) { return representation switch @@ -111,6 +136,29 @@ public static bool IsRepresentedAsDocument(IBsonSerializer serializer) return SerializationHelper.GetRepresentation(serializer) == BsonType.Document; } + public static bool IsRepresentedAsInteger(IBsonSerializer serializer) + { + var representation = GetRepresentation(serializer); + return IsIntegerRepresentation(representation); + } + + public static bool IsRepresentedAsIntegerOrNullableInteger(AggregationExpression translation) + { + return IsRepresentedAsIntegerOrNullableInteger(translation.Serializer); + } + + public static bool IsRepresentedAsIntegerOrNullableInteger(IBsonSerializer serializer) + { + if (serializer is INullableSerializer nullableSerializer) + { + return IsRepresentedAsInteger(nullableSerializer.ValueSerializer); + } + else + { + return IsRepresentedAsInteger(serializer); + } + } + public static BsonValue SerializeValue(IBsonSerializer serializer, ConstantExpression constantExpression, Expression containingExpression) { var value = constantExpression.Value; diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/TypeExtensions.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/TypeExtensions.cs index cad89047909..8ebe0777eca 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/TypeExtensions.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/TypeExtensions.cs @@ -22,6 +22,12 @@ namespace Etherna.MongoDB.Driver.Linq.Linq3Implementation.Misc { internal static class TypeExtensions { + private static readonly Type[] __dictionaryInterfaces = + { + typeof(IDictionary<,>), + typeof(IReadOnlyDictionary<,>) + }; + private static Type[] __tupleTypeDefinitions = { typeof(Tuple<>), @@ -84,11 +90,11 @@ public static bool Implements(this Type type, Type @interface) return false; } - public static bool ImplementsIDictionary(this Type type, out Type keyType, out Type valueType) + public static bool ImplementsDictionaryInterface(this Type type, out Type keyType, out Type valueType) { - if (TryGetIDictionaryGenericInterface(type, out var idictionaryType)) + if (TryGetGenericInterface(type, __dictionaryInterfaces, out var dictionaryInterface)) { - var genericArguments = idictionaryType.GetGenericArguments(); + var genericArguments = dictionaryInterface.GetGenericArguments(); keyType = genericArguments[0]; valueType = genericArguments[1]; return true; @@ -255,28 +261,15 @@ public static bool IsValueTuple(this Type type) type.IsConstructedGenericType && type.GetGenericTypeDefinition() is var typeDefinition && __valueTupleTypeDefinitions.Contains(typeDefinition); - } - public static bool TryGetIDictionaryGenericInterface(this Type type, out Type idictionaryGenericInterface) + public static bool TryGetGenericInterface(this Type type, Type[] interfaceDefinitions, out Type genericInterface) { - if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IDictionary<,>)) - { - idictionaryGenericInterface = type; - return true; - } - - foreach (var interfaceType in type.GetInterfaces()) - { - if (interfaceType.IsGenericType && interfaceType.GetGenericTypeDefinition() == typeof(IDictionary<,>)) - { - idictionaryGenericInterface = interfaceType; - return true; - } - } - - idictionaryGenericInterface = null; - return false; + genericInterface = + type.IsConstructedGenericType && interfaceDefinitions.Contains(type.GetGenericTypeDefinition()) ? + type : + type.GetInterfaces().FirstOrDefault(i => i.IsConstructedGenericType && interfaceDefinitions.Contains(i.GetGenericTypeDefinition())); + return genericInterface != null; } public static bool TryGetIEnumerableGenericInterface(this Type type, out Type ienumerableGenericInterface) diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Reflection/IDictionaryMethod.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Reflection/DictionaryMethod.cs similarity index 89% rename from src/MongoDB.Driver/Linq/Linq3Implementation/Reflection/IDictionaryMethod.cs rename to src/MongoDB.Driver/Linq/Linq3Implementation/Reflection/DictionaryMethod.cs index 61c5aa7a85b..e356b053677 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Reflection/IDictionaryMethod.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Reflection/DictionaryMethod.cs @@ -18,7 +18,7 @@ namespace Etherna.MongoDB.Driver.Linq.Linq3Implementation.Reflection { - internal static class IDictionaryMethod + internal static class DictionaryMethod { // public static methods public static bool IsGetItemWithStringMethod(MethodInfo method) @@ -29,7 +29,7 @@ public static bool IsGetItemWithStringMethod(MethodInfo method) method.GetParameters() is var parameters && parameters.Length == 1 && parameters[0].ParameterType == typeof(string) && - method.DeclaringType.ImplementsIDictionary(out var keyType, out var valueType) && + method.DeclaringType.ImplementsDictionaryInterface(out var keyType, out var valueType) && keyType == typeof(string) && method.ReturnType == valueType; } diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/BinaryExpressionToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/BinaryExpressionToAggregationExpressionTranslator.cs index 2e91d702726..6e7aa113e62 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/BinaryExpressionToAggregationExpressionTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/BinaryExpressionToAggregationExpressionTranslator.cs @@ -15,7 +15,6 @@ using System; using System.Linq.Expressions; -using Etherna.MongoDB.Bson; using Etherna.MongoDB.Bson.Serialization; using Etherna.MongoDB.Bson.Serialization.Serializers; using Etherna.MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions; @@ -82,6 +81,12 @@ public static AggregationExpression Translate(TranslationContext context, Binary rightTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, rightExpression); } + if (IsArithmeticExpression(expression)) + { + SerializationHelper.EnsureRepresentationIsNumeric(leftExpression, leftTranslation); + SerializationHelper.EnsureRepresentationIsNumeric(rightExpression, rightTranslation); + } + var ast = expression.NodeType switch { ExpressionType.Add => AstExpression.Add(leftTranslation.Ast, rightTranslation.Ast), @@ -184,7 +189,7 @@ private static bool IsAddOrSubtractExpression(Expression expression) private static bool IsArithmeticExpression(BinaryExpression expression) { - return expression.Type.IsNumeric() && IsArithmeticOperator(expression.NodeType); + return expression.Type.IsNumericOrNullableNumeric() && IsArithmeticOperator(expression.NodeType); } private static bool IsArithmeticOperator(ExpressionType nodeType) @@ -304,31 +309,29 @@ private static AggregationExpression TranslateEnumExpression(TranslationContext leftTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, leftExpression); rightTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, rightExpression); + AggregationExpression enumTranslation, operandTranslation; if (IsEnumOrConvertEnumToUnderlyingType(leftExpression)) { - serializer = leftTranslation.Serializer; + enumTranslation = leftTranslation; + operandTranslation = rightTranslation; } else { - serializer = rightTranslation.Serializer; + enumTranslation = rightTranslation; + operandTranslation = leftTranslation; } - var representation = BsonType.Int32; // assume an integer representation unless we can determine otherwise - var valueSerializer = serializer; - if (valueSerializer is INullableSerializer nullableSerializer) + if (!SerializationHelper.IsRepresentedAsIntegerOrNullableInteger(enumTranslation)) { - valueSerializer = nullableSerializer.ValueSerializer; - } - if (valueSerializer is IEnumUnderlyingTypeSerializer enumUnderlyingTypeSerializer && - enumUnderlyingTypeSerializer.EnumSerializer is IHasRepresentationSerializer withRepresentationSerializer) - { - representation = withRepresentationSerializer.Representation; + throw new ExpressionNotSupportedException(expression, because: "arithmetic on enums is only allowed when the enum is represented as an integer"); } - if (representation != BsonType.Int32 && representation != BsonType.Int64) + if (!SerializationHelper.IsRepresentedAsIntegerOrNullableInteger(operandTranslation)) { - throw new ExpressionNotSupportedException(expression, because: "arithmetic on enums is only allowed when the enum is represented as an integer"); + throw new ExpressionNotSupportedException(expression, because: "the value being added to or subtracted from an enum must be represented as an integer"); } + + serializer = enumTranslation.Serializer; } else { diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/ConvertExpressionToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/ConvertExpressionToAggregationExpressionTranslator.cs index a3ba70d612a..7936e03587d 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/ConvertExpressionToAggregationExpressionTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/ConvertExpressionToAggregationExpressionTranslator.cs @@ -30,95 +30,121 @@ public static AggregationExpression Translate(TranslationContext context, UnaryE { if (expression.NodeType == ExpressionType.Convert || expression.NodeType == ExpressionType.TypeAs) { - var expressionType = expression.Type; - if (expressionType == typeof(BsonValue)) + var sourceExpression = expression.Operand; + var sourceType = sourceExpression.Type; + var targetType = expression.Type; + + // handle double conversions like `(BsonValue)(object)x` + if (targetType == typeof(BsonValue) && + sourceExpression is UnaryExpression unarySourceExpression && + unarySourceExpression.NodeType == ExpressionType.Convert && + unarySourceExpression.Type == typeof(object)) { - return TranslateConvertToBsonValue(context, expression, expression.Operand); + sourceExpression = unarySourceExpression.Operand; } - var operandExpression = expression.Operand; - var operandTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, operandExpression); + var sourceTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, sourceExpression); + return Translate(expression, sourceType, targetType, sourceTranslation); + } - if (expressionType == operandExpression.Type) - { - return operandTranslation; - } + throw new ExpressionNotSupportedException(expression); + } - if (IsConvertEnumToUnderlyingType(expression)) - { - return TranslateConvertEnumToUnderlyingType(expression, operandTranslation); - } + private static AggregationExpression Translate(UnaryExpression expression, Type sourceType, Type targetType, AggregationExpression sourceTranslation) + { + if (targetType == sourceType) + { + return sourceTranslation; + } - if (IsConvertUnderlyingTypeToEnum(expression)) - { - return TranslateConvertUnderlyingTypeToEnum(expression, operandTranslation); - } + // from Nullable<T> must be handled before to Nullable<T> + if (IsConvertFromNullableType(sourceType)) + { + return TranslateConvertFromNullableType(expression, sourceType, targetType, sourceTranslation); + } - if (IsConvertToBaseType(sourceType: operandExpression.Type, targetType: expressionType)) - { - return TranslateConvertToBaseType(expression, operandTranslation); - } + if (IsConvertToNullableType(targetType)) + { + return TranslateConvertToNullableType(expression, sourceType, targetType, sourceTranslation); + } - if (IsConvertToDerivedType(sourceType: operandExpression.Type, targetType: expressionType)) - { - return TranslateConvertToDerivedType(expression, operandTranslation); - } + // from here on we know there are no longer any Nullable<T> types involved - if (expressionType.IsConstructedGenericType && expressionType.GetGenericTypeDefinition() == typeof(Nullable<>)) - { - var valueType = expressionType.GetGenericArguments()[0]; - if (operandExpression.Type == valueType) - { - // use the same AST but with a new nullable serializer - var nullableSerializerType = typeof(NullableSerializer<>).MakeGenericType(valueType); - var valueSerializerType = typeof(IBsonSerializer<>).MakeGenericType(valueType); - var constructorInfo = nullableSerializerType.GetConstructor(new[] { valueSerializerType }); - var nullableSerializer = (IBsonSerializer)constructorInfo.Invoke(new[] { operandTranslation.Serializer }); - return new AggregationExpression(expression, operandTranslation.Ast, nullableSerializer); - } - } + if (targetType == typeof(BsonValue)) + { + return TranslateConvertToBsonValue(expression, sourceTranslation); + } - var ast = operandTranslation.Ast; - IBsonSerializer serializer; - if (expressionType.IsInterface) - { - // when an expression is cast to an interface it's a no-op as far as we're concerned - // and we can just use the serializer for the concrete type and members not defined in the interface will just be ignored - serializer = operandTranslation.Serializer; - } - else + if (IsConvertEnumToUnderlyingType(sourceType, targetType)) + { + return TranslateConvertEnumToUnderlyingType(expression, sourceType, targetType, sourceTranslation); + } + + if (IsConvertUnderlyingTypeToEnum(sourceType, targetType)) + { + return TranslateConvertUnderlyingTypeToEnum(expression, sourceType, targetType, sourceTranslation); + } + + if (IsConvertEnumToEnum(sourceType, targetType)) + { + return TranslateConvertEnumToEnum(expression, sourceType, targetType, sourceTranslation); + } + + if (IsConvertToBaseType(sourceType, targetType)) + { + return TranslateConvertToBaseType(expression, sourceType, targetType, sourceTranslation); + } + + if (IsConvertToDerivedType(sourceType, targetType)) + { + return TranslateConvertToDerivedType(expression, targetType, sourceTranslation); + } + + var ast = sourceTranslation.Ast; + IBsonSerializer serializer; + if (targetType.IsInterface) + { + // when an expression is cast to an interface it's a no-op as far as we're concerned + // and we can just use the serializer for the concrete type and members not defined in the interface will just be ignored + serializer = sourceTranslation.Serializer; + } + else + { + AstExpression to; + switch (targetType.FullName) { - AstExpression to; - switch (expressionType.FullName) - { - case "MongoDB.Bson.ObjectId": to = "objectId"; serializer = ObjectIdSerializer.Instance; break; - case "System.Boolean": to = "bool"; serializer = BooleanSerializer.Instance; break; - case "System.DateTime": to = "date"; serializer = DateTimeSerializer.Instance; break; - case "System.Decimal": to = "decimal"; serializer = DecimalSerializer.Decimal128Instance; break; // not the default representation - case "System.Double": to = "double"; serializer = DoubleSerializer.Instance; break; - case "System.Int32": to = "int"; serializer = Int32Serializer.Instance; break; - case "System.Int64": to = "long"; serializer = Int64Serializer.Instance; break; - case "System.String": to = "string"; serializer = StringSerializer.Instance; break; - default: throw new ExpressionNotSupportedException(expression, because: $"conversion to {expressionType} is not supported"); - } - - ast = AstExpression.Convert(ast, to); + case "MongoDB.Bson.ObjectId": to = "objectId"; serializer = ObjectIdSerializer.Instance; break; + case "System.Boolean": to = "bool"; serializer = BooleanSerializer.Instance; break; + case "System.DateTime": to = "date"; serializer = DateTimeSerializer.Instance; break; + case "System.Decimal": to = "decimal"; serializer = DecimalSerializer.Decimal128Instance; break; // not the default representation + case "System.Double": to = "double"; serializer = DoubleSerializer.Instance; break; + case "System.Int32": to = "int"; serializer = Int32Serializer.Instance; break; + case "System.Int64": to = "long"; serializer = Int64Serializer.Instance; break; + case "System.String": to = "string"; serializer = StringSerializer.Instance; break; + default: throw new ExpressionNotSupportedException(expression, because: $"conversion to {targetType} is not supported"); } - return new AggregationExpression(expression, ast, serializer); + ast = AstExpression.Convert(ast, to); } - throw new ExpressionNotSupportedException(expression); + return new AggregationExpression(expression, ast, serializer); } - private static bool IsConvertEnumToUnderlyingType(UnaryExpression expression) + private static bool IsConvertEnumToEnum(Type sourceType, Type targetType) { - var sourceType = expression.Operand.Type; - var targetType = expression.Type; + return sourceType.IsEnum && targetType.IsEnum; + } + private static bool IsConvertEnumToUnderlyingType(Type sourceType, Type targetType) + { return - sourceType.IsEnumOrNullableEnum(out _, out var underlyingType) && - targetType.IsSameAsOrNullableOf(underlyingType); + sourceType.IsEnum(out var underlyingType) && + targetType == underlyingType; + } + + private static bool IsConvertFromNullableType(Type sourceType) + { + return sourceType.IsNullable(); } private static bool IsConvertToBaseType(Type sourceType, Type targetType) @@ -131,88 +157,113 @@ private static bool IsConvertToDerivedType(Type sourceType, Type targetType) return targetType.IsSubclassOf(sourceType); } - private static bool IsConvertUnderlyingTypeToEnum(UnaryExpression expression) + private static bool IsConvertToNullableType(Type targetType) { - var sourceType = expression.Operand.Type; - var targetType = expression.Type; + return targetType.IsNullable(); + } + private static bool IsConvertUnderlyingTypeToEnum(Type sourceType, Type targetType) + { return - targetType.IsEnumOrNullableEnum(out _, out var underlyingType) && - sourceType.IsSameAsOrNullableOf(underlyingType); + targetType.IsEnum(out var underlyingType) && + sourceType == underlyingType; } - private static AggregationExpression TranslateConvertToBaseType(UnaryExpression expression, AggregationExpression operandTranslation) + private static AggregationExpression TranslateConvertToBaseType(UnaryExpression expression, Type sourceType, Type targetType, AggregationExpression sourceTranslation) { - var baseType = expression.Type; - var derivedType = expression.Operand.Type; - var derivedTypeSerializer = operandTranslation.Serializer; - var downcastingSerializer = DowncastingSerializer.Create(baseType, derivedType, derivedTypeSerializer); + var derivedTypeSerializer = sourceTranslation.Serializer; + var downcastingSerializer = DowncastingSerializer.Create(targetType, sourceType, derivedTypeSerializer); - return new AggregationExpression(expression, operandTranslation.Ast, downcastingSerializer); + return new AggregationExpression(expression, sourceTranslation.Ast, downcastingSerializer); } - private static AggregationExpression TranslateConvertToDerivedType(UnaryExpression expression, AggregationExpression operandTranslation) + private static AggregationExpression TranslateConvertToDerivedType(UnaryExpression expression, Type targetType, AggregationExpression sourceTranslation) { - var serializer = BsonSerializer.LookupSerializer(expression.Type); + var serializer = BsonSerializer.LookupSerializer(targetType); - return new AggregationExpression(expression, operandTranslation.Ast, serializer); + return new AggregationExpression(expression, sourceTranslation.Ast, serializer); } - private static AggregationExpression TranslateConvertToBsonValue(TranslationContext context, UnaryExpression expression, Expression operand) + private static AggregationExpression TranslateConvertToBsonValue(UnaryExpression expression, AggregationExpression sourceTranslation) { - // handle double conversions like `(BsonValue)(object)x.Anything` - if (operand is UnaryExpression unaryExpression && - unaryExpression.NodeType == ExpressionType.Convert && - unaryExpression.Type == typeof(object)) - { - operand = unaryExpression.Operand; - } - - var operandTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, operand); - - return new AggregationExpression(expression, operandTranslation.Ast, BsonValueSerializer.Instance); + return new AggregationExpression(expression, sourceTranslation.Ast, BsonValueSerializer.Instance); } - private static AggregationExpression TranslateConvertEnumToUnderlyingType(UnaryExpression expression, AggregationExpression operandTranslation) + private static AggregationExpression TranslateConvertEnumToEnum(UnaryExpression expression, Type sourceType, Type targetType, AggregationExpression sourceTranslation) { - var sourceType = expression.Operand.Type; - var targetType = expression.Type; - - IBsonSerializer enumSerializer; - if (sourceType.IsNullable()) + if (!sourceType.IsEnum) { - var nullableSerializer = (INullableSerializer)operandTranslation.Serializer; - enumSerializer = nullableSerializer.ValueSerializer; + throw new ExpressionNotSupportedException(expression, because: "source type is not an enum"); } - else + if (!targetType.IsEnum) { - enumSerializer = operandTranslation.Serializer; + throw new ExpressionNotSupportedException(expression, because: "target type is not an enum"); } - IBsonSerializer targetSerializer; - var enumUnderlyingTypeSerializer = EnumUnderlyingTypeSerializer.Create(enumSerializer); - if (targetType.IsNullable()) + var sourceSerializer = sourceTranslation.Serializer; + if (sourceSerializer is IHasRepresentationSerializer sourceHasRepresentationSerializer && + !SerializationHelper.IsNumericRepresentation(sourceHasRepresentationSerializer.Representation)) { - targetSerializer = NullableSerializer.Create(enumUnderlyingTypeSerializer); + throw new ExpressionNotSupportedException(expression, because: "source enum is not represented as a number"); } - else + + var targetSerializer = EnumSerializer.Create(targetType); + return new AggregationExpression(expression, sourceTranslation.Ast, targetSerializer); + } + + private static AggregationExpression TranslateConvertEnumToUnderlyingType(UnaryExpression expression, Type sourceType, Type targetType, AggregationExpression sourceTranslation) + { + var enumSerializer = sourceTranslation.Serializer; + var targetSerializer = EnumUnderlyingTypeSerializer.Create(enumSerializer); + return new AggregationExpression(expression, sourceTranslation.Ast, targetSerializer); + } + + private static AggregationExpression TranslateConvertFromNullableType(UnaryExpression expression, Type sourceType, Type targetType, AggregationExpression sourceTranslation) + { + if (sourceType.IsNullable(out var sourceValueType)) { - targetSerializer = enumUnderlyingTypeSerializer; + var (sourceVarBinding, sourceAst) = AstExpression.UseVarIfNotSimple("source", sourceTranslation.Ast); + var sourceNullableSerializer = (INullableSerializer)sourceTranslation.Serializer; + var sourceValueSerializer = sourceNullableSerializer.ValueSerializer; + var sourceValueAggregationExpression = new AggregationExpression(expression.Operand, sourceAst, sourceValueSerializer); + var convertTranslation = Translate(expression, sourceValueType, targetType, sourceValueAggregationExpression); + + // note: we would have liked to throw a query execution error here if the value is null and the target type is not nullable but there is no way to do that in MQL + // so we just return null instead and the user must check for null themselves if they want to define what happens when the value is null + // but see SERVER-78092 and the proposed $error operator + + var ast = AstExpression.Let( + sourceVarBinding, + AstExpression.Cond(AstExpression.Eq(sourceAst, BsonNull.Value), BsonNull.Value, convertTranslation.Ast)); + + return new AggregationExpression(expression, ast, convertTranslation.Serializer); } - return new AggregationExpression(expression, operandTranslation.Ast, targetSerializer); + throw new ExpressionNotSupportedException(expression, because: "sourceType is not nullable"); } - private static AggregationExpression TranslateConvertUnderlyingTypeToEnum(UnaryExpression expression, AggregationExpression operandTranslation) + private static AggregationExpression TranslateConvertToNullableType(UnaryExpression expression, Type sourceType, Type targetType, AggregationExpression sourceTranslation) { - var targetType = expression.Type; + if (sourceType.IsNullable()) + { + // ConvertFromNullableType should have been called first + throw new ExpressionNotSupportedException(expression, because: "sourceType is nullable"); + } - var valueSerializer = operandTranslation.Serializer; - if (valueSerializer is INullableSerializer nullableSerializer) + if (targetType.IsNullable(out var targetValueType)) { - valueSerializer = nullableSerializer.ValueSerializer; + var convertTranslation = Translate(expression, sourceType, targetValueType, sourceTranslation); + var nullableSerializer = NullableSerializer.Create(convertTranslation.Serializer); + return new AggregationExpression(expression, convertTranslation.Ast, nullableSerializer); } + throw new ExpressionNotSupportedException(expression, because: "targetType is not nullable"); + } + + private static AggregationExpression TranslateConvertUnderlyingTypeToEnum(UnaryExpression expression, Type sourceType, Type targetType, AggregationExpression sourceTranslation) + { + var valueSerializer = sourceTranslation.Serializer; + IBsonSerializer targetSerializer; if (valueSerializer is IEnumUnderlyingTypeSerializer enumUnderlyingTypeSerializer) { @@ -220,21 +271,10 @@ private static AggregationExpression TranslateConvertUnderlyingTypeToEnum(UnaryE } else { - var enumType = targetType; - if (targetType.IsNullable(out var wrappedType)) - { - enumType = wrappedType; - } - - targetSerializer = EnumSerializer.Create(enumType); - } - - if (targetType.IsNullableEnum()) - { - targetSerializer = NullableSerializer.Create(targetSerializer); + targetSerializer = EnumSerializer.Create(targetType); } - return new AggregationExpression(expression, operandTranslation.Ast, targetSerializer); + return new AggregationExpression(expression, sourceTranslation.Ast, targetSerializer); } } } diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/AbsMethodToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/AbsMethodToAggregationExpressionTranslator.cs index f610b1847c7..a87b5db3091 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/AbsMethodToAggregationExpressionTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/AbsMethodToAggregationExpressionTranslator.cs @@ -43,6 +43,7 @@ public static AggregationExpression Translate(TranslationContext context, Method { var valueExpression = ConvertHelper.RemoveWideningConvert(arguments[0]); var valueTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, valueExpression); + SerializationHelper.EnsureRepresentationIsNumeric(valueExpression, valueTranslation); var ast = AstExpression.Abs(valueTranslation.Ast); return new AggregationExpression(expression, ast, valueTranslation.Serializer); } diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/CeilingMethodToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/CeilingMethodToAggregationExpressionTranslator.cs index 59490e275b9..a690cbdebc2 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/CeilingMethodToAggregationExpressionTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/CeilingMethodToAggregationExpressionTranslator.cs @@ -32,6 +32,7 @@ public static AggregationExpression Translate(TranslationContext context, Method { var argumentExpression = ConvertHelper.RemoveWideningConvert(arguments[0]); var argumentTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, argumentExpression); + SerializationHelper.EnsureRepresentationIsNumeric(argumentExpression, argumentTranslation); var ast = AstExpression.Ceil(argumentTranslation.Ast); var serializer = BsonSerializer.LookupSerializer(expression.Type); return new AggregationExpression(expression, ast, serializer); diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/DateTimeAddOrSubtractMethodToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/DateTimeAddOrSubtractMethodToAggregationExpressionTranslator.cs index db4b081ee2b..447fa1d4a8a 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/DateTimeAddOrSubtractMethodToAggregationExpressionTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/DateTimeAddOrSubtractMethodToAggregationExpressionTranslator.cs @@ -144,12 +144,9 @@ public static AggregationExpression Translate(TranslationContext context, Method { throw new ExpressionNotSupportedException(valueExpression, expression); } - var representation = timeSpanSerializer.Representation; + SerializationHelper.EnsureRepresentationIsNumeric(valueExpression, timeSpanSerializer); + var serializerUnits = timeSpanSerializer.Units; - if (representation != BsonType.Int32 && representation != BsonType.Int64 && representation != BsonType.Double) - { - throw new ExpressionNotSupportedException(valueExpression, expression); - } (unit, amount) = serializerUnits switch { TimeSpanUnits.Ticks => ("millisecond", AstExpression.Divide(valueTranslation.Ast, (double)TimeSpan.TicksPerMillisecond)), @@ -174,6 +171,8 @@ public static AggregationExpression Translate(TranslationContext context, Method else { var valueTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, valueExpression); + SerializationHelper.EnsureRepresentationIsNumeric(valueExpression, valueTranslation); + (unit, amount) = method.Name switch { "AddTicks" => ("millisecond", AstExpression.Divide(valueTranslation.Ast, (double)TimeSpan.TicksPerMillisecond)), diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/ExpMethodToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/ExpMethodToAggregationExpressionTranslator.cs index d8e3ade3472..410a64c4940 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/ExpMethodToAggregationExpressionTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/ExpMethodToAggregationExpressionTranslator.cs @@ -32,6 +32,7 @@ public static AggregationExpression Translate(TranslationContext context, Method { var argumentExpression = ConvertHelper.RemoveWideningConvert(arguments[0]); var argumentTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, argumentExpression); + SerializationHelper.EnsureRepresentationIsNumeric(argumentExpression, argumentTranslation); var ast = AstExpression.Exp(argumentTranslation.Ast); return new AggregationExpression(expression, ast, new DoubleSerializer()); } diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/FloorMethodToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/FloorMethodToAggregationExpressionTranslator.cs index 343a17480d9..26888e33705 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/FloorMethodToAggregationExpressionTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/FloorMethodToAggregationExpressionTranslator.cs @@ -32,6 +32,7 @@ public static AggregationExpression Translate(TranslationContext context, Method { var argumentExpression = ConvertHelper.RemoveWideningConvert(arguments[0]); var argumentTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, argumentExpression); + SerializationHelper.EnsureRepresentationIsNumeric(argumentExpression, argumentTranslation); var ast = AstExpression.Floor(argumentTranslation.Ast); var serializer = BsonSerializer.LookupSerializer(expression.Type); return new AggregationExpression(expression, ast, serializer); diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/GetItemMethodToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/GetItemMethodToAggregationExpressionTranslator.cs index e5b30b00f44..295363e9c1a 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/GetItemMethodToAggregationExpressionTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/GetItemMethodToAggregationExpressionTranslator.cs @@ -55,7 +55,7 @@ public static AggregationExpression Translate(TranslationContext context, Expres return TranslateIListGetItemWithInt(context, expression, sourceExpression, arguments[0]); } - if (IDictionaryMethod.IsGetItemWithStringMethod(method)) + if (DictionaryMethod.IsGetItemWithStringMethod(method)) { return TranslateIDictionaryGetItemWithString(context, expression, sourceExpression, arguments[0]); } diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/IndexOfAnyMethodToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/IndexOfAnyMethodToAggregationExpressionTranslator.cs index 987c99a4fc0..d46743f0d16 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/IndexOfAnyMethodToAggregationExpressionTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/IndexOfAnyMethodToAggregationExpressionTranslator.cs @@ -115,6 +115,7 @@ string TranslateAnyOf(ReadOnlyCollection<Expression> arguments) var startIndexExpression = arguments[1]; var startIndexTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, startIndexExpression); + SerializationHelper.EnsureRepresentationIsNumeric(startIndexExpression, startIndexTranslation); return AstExpression.UseVarIfNotSimple("startIndex", startIndexTranslation.Ast); } @@ -127,6 +128,7 @@ string TranslateAnyOf(ReadOnlyCollection<Expression> arguments) var countExpression = arguments[2]; var countTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, countExpression); + SerializationHelper.EnsureRepresentationIsNumeric(countExpression, countTranslation); return AstExpression.UseVarIfNotSimple("count", countTranslation.Ast); } diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/IndexOfMethodToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/IndexOfMethodToAggregationExpressionTranslator.cs index 51de8f81303..c0b8c462fd8 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/IndexOfMethodToAggregationExpressionTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/IndexOfMethodToAggregationExpressionTranslator.cs @@ -83,8 +83,18 @@ public static AggregationExpression Translate(TranslationContext context, Method { var objectTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, objectExpression); var valueTranslation = TranslateValue(); - var startIndexTranslation = startIndexExpression == null ? null : ExpressionToAggregationExpressionTranslator.Translate(context, startIndexExpression); - var countTranslation = countExpression == null ? null : ExpressionToAggregationExpressionTranslator.Translate(context, countExpression); + AggregationExpression startIndexTranslation = null; + if (startIndexExpression != null) + { + startIndexTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, startIndexExpression); + SerializationHelper.EnsureRepresentationIsNumeric(startIndexExpression, startIndexTranslation); + } + AggregationExpression countTranslation = null; + if (countExpression != null) + { + countTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, countExpression); + SerializationHelper.EnsureRepresentationIsNumeric(countExpression, countTranslation); + } var ordinal = GetOrdinalFromComparisonType(); var endAst = CreateEndAst(startIndexTranslation?.Ast, countTranslation?.Ast); diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/LogMethodToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/LogMethodToAggregationExpressionTranslator.cs index c60bc6a2fb5..a95685e064b 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/LogMethodToAggregationExpressionTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/LogMethodToAggregationExpressionTranslator.cs @@ -32,6 +32,7 @@ public static AggregationExpression Translate(TranslationContext context, Method { var argumentExpression = ConvertHelper.RemoveWideningConvert(arguments[0]); var argumentTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, argumentExpression); + SerializationHelper.EnsureRepresentationIsNumeric(argumentExpression, argumentTranslation); AstExpression ast; if (method.Is(MathMethod.LogWithNewBase)) { diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/PowMethodToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/PowMethodToAggregationExpressionTranslator.cs index 79aae68c1c6..ca3d64f87b1 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/PowMethodToAggregationExpressionTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/PowMethodToAggregationExpressionTranslator.cs @@ -32,8 +32,10 @@ public static AggregationExpression Translate(TranslationContext context, Method { var xExpression = ConvertHelper.RemoveWideningConvert(arguments[0]); var xTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, xExpression); + SerializationHelper.EnsureRepresentationIsNumeric(xExpression, xTranslation); var yExpression = ConvertHelper.RemoveWideningConvert(arguments[1]); var yTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, yExpression); + SerializationHelper.EnsureRepresentationIsNumeric(yExpression, yTranslation); var ast = AstExpression.Pow(xTranslation.Ast, yTranslation.Ast); return new AggregationExpression(expression, ast, new DoubleSerializer()); } diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/RangeMethodToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/RangeMethodToAggregationExpressionTranslator.cs index 031a8641c1c..a1a1f004684 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/RangeMethodToAggregationExpressionTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/RangeMethodToAggregationExpressionTranslator.cs @@ -34,10 +34,14 @@ public static AggregationExpression Translate(TranslationContext context, Method { var startExpression = arguments[0]; var startTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, startExpression); - var (startVar, startAst) = AstExpression.UseVarIfNotSimple("start", startTranslation.Ast); + SerializationHelper.EnsureRepresentationIsNumeric(startExpression, startTranslation); var countExpression = arguments[1]; var countTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, countExpression); + SerializationHelper.EnsureRepresentationIsNumeric(countExpression, countTranslation); + + var (startVar, startAst) = AstExpression.UseVarIfNotSimple("start", startTranslation.Ast); var (countVar, countAst) = AstExpression.UseVarIfNotSimple("count", countTranslation.Ast); + var ast = AstExpression.Let( startVar, countVar, diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/RoundMethodToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/RoundMethodToAggregationExpressionTranslator.cs index da58bdeb44d..34a98e72cdb 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/RoundMethodToAggregationExpressionTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/RoundMethodToAggregationExpressionTranslator.cs @@ -46,12 +46,14 @@ public static AggregationExpression Translate(TranslationContext context, Method { var argumentExpression = ConvertHelper.RemoveWideningConvert(arguments[0]); var argumentTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, argumentExpression); + SerializationHelper.EnsureRepresentationIsNumeric(argumentExpression, argumentTranslation); AstExpression ast; if (method.IsOneOf(__roundWithPlaceMethods)) { var placeExpression = arguments[1]; var placeTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, placeExpression); + SerializationHelper.EnsureRepresentationIsNumeric(placeExpression, placeTranslation); ast = AstExpression.Round(argumentTranslation.Ast, placeTranslation.Ast); } else diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/SqrtMethodToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/SqrtMethodToAggregationExpressionTranslator.cs index 327c2dde33e..4c3bf4fab20 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/SqrtMethodToAggregationExpressionTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/SqrtMethodToAggregationExpressionTranslator.cs @@ -32,6 +32,7 @@ public static AggregationExpression Translate(TranslationContext context, Method { var argumentExpression = ConvertHelper.RemoveWideningConvert(arguments[0]); var argumentTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, argumentExpression); + SerializationHelper.EnsureRepresentationIsNumeric(argumentExpression, argumentTranslation); var ast = AstExpression.Sqrt(argumentTranslation.Ast); return new AggregationExpression(expression, ast, new DoubleSerializer()); } diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/SubstringMethodToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/SubstringMethodToAggregationExpressionTranslator.cs index 2b1fc4f07bb..4790110f022 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/SubstringMethodToAggregationExpressionTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/SubstringMethodToAggregationExpressionTranslator.cs @@ -53,6 +53,7 @@ private static AggregationExpression TranslateHelper(TranslationContext context, { var stringTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, stringExpression); var startIndexTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, startIndexExpression); + SerializationHelper.EnsureRepresentationIsNumeric(startIndexExpression, startIndexTranslation); AstExpression ast; if (lengthExpression == null) diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/TruncateMethodToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/TruncateMethodToAggregationExpressionTranslator.cs index 39932cb5a6f..7246b81b6a5 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/TruncateMethodToAggregationExpressionTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/TruncateMethodToAggregationExpressionTranslator.cs @@ -73,6 +73,7 @@ public static AggregationExpression Translate(TranslationContext context, Method { var argumentExpression = ConvertHelper.RemoveWideningConvert(arguments[0]); var argumentTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, argumentExpression); + SerializationHelper.EnsureRepresentationIsNumeric(argumentExpression, argumentTranslation); var ast = AstExpression.Trunc(argumentTranslation.Ast); return new AggregationExpression(expression, ast, argumentTranslation.Serializer); } diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/NegateExpressionToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/NegateExpressionToAggregationExpressionTranslator.cs index 08757429de3..057b8afa1b3 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/NegateExpressionToAggregationExpressionTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/NegateExpressionToAggregationExpressionTranslator.cs @@ -26,7 +26,7 @@ public static AggregationExpression Translate(TranslationContext context, UnaryE if (expression.NodeType == ExpressionType.Negate) { var operandTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, expression.Operand); - SerializationHelper.EnsureRepresentationIsNumeric(expression, operandTranslation.Serializer); + SerializationHelper.EnsureRepresentationIsNumeric(expression, operandTranslation); var ast = AstExpression.Subtract(0, operandTranslation.Ast); return new AggregationExpression(expression, ast, operandTranslation.Serializer); diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/NewArrayInitExpressionToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/NewArrayInitExpressionToAggregationExpressionTranslator.cs index c0f337f35f1..e682522de22 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/NewArrayInitExpressionToAggregationExpressionTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/NewArrayInitExpressionToAggregationExpressionTranslator.cs @@ -13,8 +13,11 @@ * limitations under the License. */ +using System; using System.Collections.Generic; using System.Linq.Expressions; +using Etherna.MongoDB.Bson.Serialization; +using Etherna.MongoDB.Bson.Serialization.Serializers; using Etherna.MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions; namespace Etherna.MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators @@ -24,14 +27,29 @@ internal static class NewArrayInitExpressionToAggregationExpressionTranslator public static AggregationExpression Translate(TranslationContext context, NewArrayExpression expression) { var items = new List<AstExpression>(); + IBsonSerializer itemSerializer = null; foreach (var itemExpression in expression.Expressions) { var itemTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, itemExpression); items.Add(itemTranslation.Ast); + itemSerializer ??= itemTranslation.Serializer; + + // make sure all items are serialized using the same serializer + if (!itemTranslation.Serializer.Equals(itemSerializer)) + { + throw new ExpressionNotSupportedException(expression, because: "all items in the array must be serialized using the same serializer"); + } } + var ast = AstExpression.ComputedArray(items); - var serializer = context.KnownSerializersRegistry.GetSerializer(expression); - return new AggregationExpression(expression, ast, serializer); + + var arrayType = expression.Type; + var itemType = arrayType.GetElementType(); + itemSerializer ??= BsonSerializer.LookupSerializer(itemType); // if the array is empty itemSerializer will be null + var arraySerializerType = typeof(ArraySerializer<>).MakeGenericType(itemType); + var arraySerializer = (IBsonSerializer)Activator.CreateInstance(arraySerializerType, itemSerializer); + + return new AggregationExpression(expression, ast, arraySerializer); } } } diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ToFilterFieldTranslators/GetItemMethodToFilterFieldTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ToFilterFieldTranslators/GetItemMethodToFilterFieldTranslator.cs index 1223f0fed9b..40f96373bf5 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ToFilterFieldTranslators/GetItemMethodToFilterFieldTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ToFilterFieldTranslators/GetItemMethodToFilterFieldTranslator.cs @@ -53,9 +53,9 @@ public static AstFilterField Translate(TranslationContext context, Expression ex return TranslateIListGetItemWithInt(context, expression, fieldExpression, arguments[0]); } - if (IDictionaryMethod.IsGetItemWithStringMethod(method)) + if (DictionaryMethod.IsGetItemWithStringMethod(method)) { - return TranslateIDictionaryGetItemWithString(context, expression, fieldExpression, arguments[0]); + return TranslateDictionaryGetItemWithString(context, expression, fieldExpression, arguments[0]); } throw new ExpressionNotSupportedException(expression); @@ -80,7 +80,7 @@ private static AstFilterField TranslateIListGetItemWithInt(TranslationContext co return ArrayIndexExpressionToFilterFieldTranslator.Translate(context, expression, fieldExpression, indexExpression); } - private static AstFilterField TranslateIDictionaryGetItemWithString(TranslationContext context, Expression expression, Expression fieldExpression, Expression keyExpression) + private static AstFilterField TranslateDictionaryGetItemWithString(TranslationContext context, Expression expression, Expression fieldExpression, Expression keyExpression) { var field = ExpressionToFilterFieldTranslator.Translate(context, fieldExpression); var key = keyExpression.GetConstantValue<string>(containingExpression: expression); diff --git a/src/MongoDB.Driver/ProjectionDefinition.cs b/src/MongoDB.Driver/ProjectionDefinition.cs index ad357dd79db..ab9d1007828 100644 --- a/src/MongoDB.Driver/ProjectionDefinition.cs +++ b/src/MongoDB.Driver/ProjectionDefinition.cs @@ -306,6 +306,21 @@ public Expression<Func<TSource, TProjection>> Expression /// <inheritdoc /> public override RenderedProjectionDefinition<TProjection> Render(IBsonSerializer<TSource> sourceSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider) + { + if (linqProvider == LinqProvider.V2) + { + // this is slightly wrong because we're not actually rendering for a Find + // but this is required to avoid a regression with LINQ2 + return RenderForFind(sourceSerializer, serializerRegistry, linqProvider); + } + else + { + return linqProvider.GetAdapter().TranslateExpressionToProjection(_expression, sourceSerializer, serializerRegistry, translationOptions: null); + } + } + + /// <inheritdoc /> + internal override RenderedProjectionDefinition<TProjection> RenderForFind(IBsonSerializer<TSource> sourceSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider) { return linqProvider.GetAdapter().TranslateExpressionToFindProjection(_expression, sourceSerializer, serializerRegistry); } diff --git a/src/MongoDB.Driver/ProjectionDefinitionBuilder.cs b/src/MongoDB.Driver/ProjectionDefinitionBuilder.cs index 265282a5296..198b655323e 100644 --- a/src/MongoDB.Driver/ProjectionDefinitionBuilder.cs +++ b/src/MongoDB.Driver/ProjectionDefinitionBuilder.cs @@ -544,7 +544,9 @@ public ProjectionDefinition<TSource> Exclude(Expression<Func<TSource, object>> f /// </returns> public ProjectionDefinition<TSource, TProjection> Expression<TProjection>(Expression<Func<TSource, TProjection>> expression) { - return new ExpressionProjectionDefinition<TSource, TProjection>(expression, null); + // TODO: replace FindExpressionProjectionDefinition with ExpressionProjectionDefinition when LINQ2 is removed + // in the meantime we have to keep using FindExpressionProjectionDefinition here for compatibility with LINQ2 + return new FindExpressionProjectionDefinition<TSource, TProjection>(expression); } /// <summary> diff --git a/src/MongoDB.Driver/Properties/AssemblyInfo.cs b/src/MongoDB.Driver/Properties/AssemblyInfo.cs index 14ec55e555a..c4ac8277c9f 100644 --- a/src/MongoDB.Driver/Properties/AssemblyInfo.cs +++ b/src/MongoDB.Driver/Properties/AssemblyInfo.cs @@ -25,11 +25,12 @@ // Required for most of the reflection usage in Xamarin.iOS/Xamarin.Mac. [assembly: Preserve(AllMembers = true)] -[assembly: InternalsVisibleTo("MongoDB.Driver.Legacy")] -[assembly: InternalsVisibleTo("MongoDB.Driver.Legacy.TestHelpers")] -[assembly: InternalsVisibleTo("MongoDB.Driver.Legacy.Tests")] -[assembly: InternalsVisibleTo("MongoDB.Driver.Tests")] -[assembly: InternalsVisibleTo("MongoDB.Driver.TestHelpers")] -[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] -[assembly: InternalsVisibleTo("MongoDB.Driver.Benchmarks")] +[assembly: InternalsVisibleTo("MongoDB.Driver.Legacy, PublicKey=002400000480000094000000060200000024000052534131000400000100010035287f0d3883c0a075c88e0cda3ce93b621003ecbd5e920d4a8c7238564f4d2f4f68116aca28c9b21341dc3a877679c14556192b2b2f5fe2c11d624e0894d308ff7b94bf6fd72aef1b41017ffe2572e99019d1c61963e68cd0ed67734a42cb333b808e3867cbe631937214e32e409fb1fa62fdb69d494c2530e64a40e417d6ee")] +[assembly: InternalsVisibleTo("MongoDB.Driver.Legacy.TestHelpers, PublicKey=002400000480000094000000060200000024000052534131000400000100010035287f0d3883c0a075c88e0cda3ce93b621003ecbd5e920d4a8c7238564f4d2f4f68116aca28c9b21341dc3a877679c14556192b2b2f5fe2c11d624e0894d308ff7b94bf6fd72aef1b41017ffe2572e99019d1c61963e68cd0ed67734a42cb333b808e3867cbe631937214e32e409fb1fa62fdb69d494c2530e64a40e417d6ee")] +[assembly: InternalsVisibleTo("MongoDB.Driver.Legacy.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010035287f0d3883c0a075c88e0cda3ce93b621003ecbd5e920d4a8c7238564f4d2f4f68116aca28c9b21341dc3a877679c14556192b2b2f5fe2c11d624e0894d308ff7b94bf6fd72aef1b41017ffe2572e99019d1c61963e68cd0ed67734a42cb333b808e3867cbe631937214e32e409fb1fa62fdb69d494c2530e64a40e417d6ee")] +[assembly: InternalsVisibleTo("MongoDB.Driver.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010035287f0d3883c0a075c88e0cda3ce93b621003ecbd5e920d4a8c7238564f4d2f4f68116aca28c9b21341dc3a877679c14556192b2b2f5fe2c11d624e0894d308ff7b94bf6fd72aef1b41017ffe2572e99019d1c61963e68cd0ed67734a42cb333b808e3867cbe631937214e32e409fb1fa62fdb69d494c2530e64a40e417d6ee")] +[assembly: InternalsVisibleTo("MongoDB.Driver.TestHelpers, PublicKey=002400000480000094000000060200000024000052534131000400000100010035287f0d3883c0a075c88e0cda3ce93b621003ecbd5e920d4a8c7238564f4d2f4f68116aca28c9b21341dc3a877679c14556192b2b2f5fe2c11d624e0894d308ff7b94bf6fd72aef1b41017ffe2572e99019d1c61963e68cd0ed67734a42cb333b808e3867cbe631937214e32e409fb1fa62fdb69d494c2530e64a40e417d6ee")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] +[assembly: InternalsVisibleTo("MongoDB.Driver.Benchmarks, PublicKey=002400000480000094000000060200000024000052534131000400000100010035287f0d3883c0a075c88e0cda3ce93b621003ecbd5e920d4a8c7238564f4d2f4f68116aca28c9b21341dc3a877679c14556192b2b2f5fe2c11d624e0894d308ff7b94bf6fd72aef1b41017ffe2572e99019d1c61963e68cd0ed67734a42cb333b808e3867cbe631937214e32e409fb1fa62fdb69d494c2530e64a40e417d6ee")] +[assembly: InternalsVisibleTo("MongoDB.Analyzer.MQLGenerator, PublicKey=002400000480000094000000060200000024000052534131000400000100010035287f0d3883c0a075c88e0cda3ce93b621003ecbd5e920d4a8c7238564f4d2f4f68116aca28c9b21341dc3a877679c14556192b2b2f5fe2c11d624e0894d308ff7b94bf6fd72aef1b41017ffe2572e99019d1c61963e68cd0ed67734a42cb333b808e3867cbe631937214e32e409fb1fa62fdb69d494c2530e64a40e417d6ee")] diff --git a/src/MongoDB.Driver/Support/ReflectionExtensions.cs b/src/MongoDB.Driver/Support/ReflectionExtensions.cs index b3c6ad6d09c..aba0c9068b7 100644 --- a/src/MongoDB.Driver/Support/ReflectionExtensions.cs +++ b/src/MongoDB.Driver/Support/ReflectionExtensions.cs @@ -72,6 +72,21 @@ public static bool IsNumeric(this Type type) type == typeof(Decimal128); } + public static bool IsNumericOrNullableNumeric(this Type type) + { + if (type.IsConstructedGenericType && + type.GetGenericTypeDefinition() is var genericTypeDefinition && + genericTypeDefinition == typeof(Nullable<>)) + { + var valueType = type.GetGenericArguments()[0]; + return IsNumeric(valueType); + } + else + { + return IsNumeric(type); + } + } + public static bool IsConvertibleToEnum(this Type type) { return