diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Reflection/StringMethod.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Reflection/StringMethod.cs index 279e382733c..41ef1422449 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Reflection/StringMethod.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Reflection/StringMethod.cs @@ -72,6 +72,7 @@ internal static class StringMethod private static readonly MethodInfo __startsWithWithString; private static readonly MethodInfo __startsWithWithStringAndComparisonType; private static readonly MethodInfo __startsWithWithStringAndIgnoreCaseAndCulture; + private static readonly MethodInfo __staticCompare; private static readonly MethodInfo __stringInWithEnumerable; private static readonly MethodInfo __stringInWithParams; private static readonly MethodInfo __stringNinWithEnumerable; @@ -151,6 +152,7 @@ static StringMethod() __startsWithWithString = ReflectionInfo.Method((string s, string value) => s.StartsWith(value)); __startsWithWithStringAndComparisonType = ReflectionInfo.Method((string s, string value, StringComparison comparisonType) => s.StartsWith(value, comparisonType)); __startsWithWithStringAndIgnoreCaseAndCulture = ReflectionInfo.Method((string s, string value, bool ignoreCase, CultureInfo culture) => s.StartsWith(value, ignoreCase, culture)); + __staticCompare = ReflectionInfo.Method((string strA, string strB) => String.Compare(strA, strB)); __stringInWithEnumerable = ReflectionInfo.Method((string s, IEnumerable values) => s.StringIn(values)); __stringInWithParams = ReflectionInfo.Method((string s, StringOrRegularExpression[] values) => s.StringIn(values)); __stringNinWithEnumerable = ReflectionInfo.Method((string s, IEnumerable values) => s.StringNin(values)); @@ -220,6 +222,7 @@ static StringMethod() public static MethodInfo StartsWithWithString => __startsWithWithString; public static MethodInfo StartsWithWithStringAndComparisonType => __startsWithWithStringAndComparisonType; public static MethodInfo StartsWithWithStringAndIgnoreCaseAndCulture => __startsWithWithStringAndIgnoreCaseAndCulture; + public static MethodInfo StaticCompare => __staticCompare; public static MethodInfo StringInWithEnumerable => __stringInWithEnumerable; public static MethodInfo StringInWithParams => __stringInWithParams; public static MethodInfo StringNinWithEnumerable => __stringNinWithEnumerable; diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodCallExpressionToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodCallExpressionToAggregationExpressionTranslator.cs index 4dbbe32fdd7..14942e6b793 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodCallExpressionToAggregationExpressionTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodCallExpressionToAggregationExpressionTranslator.cs @@ -33,6 +33,7 @@ public static TranslatedExpression Translate(TranslationContext context, MethodC case "AsQueryable": return AsQueryableMethodToAggregationExpressionTranslator.Translate(context, expression); case "Average": return AverageMethodToAggregationExpressionTranslator.Translate(context, expression); case "Ceiling": return CeilingMethodToAggregationExpressionTranslator.Translate(context, expression); + case "Compare": return CompareMethodToAggregationExpressionTranslator.Translate(context, expression); case "CompareTo": return CompareToMethodToAggregationExpressionTranslator.Translate(context, expression); case "Concat": return ConcatMethodToAggregationExpressionTranslator.Translate(context, expression); case "Constant": return ConstantMethodToAggregationExpressionTranslator.Translate(context, expression); diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/CompareMethodToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/CompareMethodToAggregationExpressionTranslator.cs new file mode 100644 index 00000000000..df7a3d27b5f --- /dev/null +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/CompareMethodToAggregationExpressionTranslator.cs @@ -0,0 +1,43 @@ +/* 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.Expressions; +using MongoDB.Bson.Serialization.Serializers; +using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions; +using MongoDB.Driver.Linq.Linq3Implementation.Misc; + +namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.MethodTranslators +{ + internal static class CompareMethodToAggregationExpressionTranslator + { + public static TranslatedExpression Translate(TranslationContext context, MethodCallExpression expression) + { + var method = expression.Method; + var arguments = expression.Arguments; + + if (method.Is(StringMethod.StaticCompare)) + { + var strAExpression = arguments[0]; + var strATranslation = ExpressionToAggregationExpressionTranslator.Translate(context, strAExpression); + var strBExpression = arguments[1]; + var strBTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, strBExpression); + var ast = AstExpression.Cmp(strATranslation.Ast, strBTranslation.Ast); + return new TranslatedExpression(expression, ast, Int32Serializer.Instance); + } + + throw new ExpressionNotSupportedException(expression); + } + } +} diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ExpressionTranslators/CompareToComparisonExpressionToFilterTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ExpressionTranslators/CompareToComparisonExpressionToFilterTranslator.cs index 500395dbf42..438ab7d4068 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ExpressionTranslators/CompareToComparisonExpressionToFilterTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ExpressionTranslators/CompareToComparisonExpressionToFilterTranslator.cs @@ -14,6 +14,7 @@ */ using System.Linq.Expressions; +using System.Reflection; using MongoDB.Driver.Linq.Linq3Implementation.Ast.Filters; using MongoDB.Driver.Linq.Linq3Implementation.ExtensionMethods; using MongoDB.Driver.Linq.Linq3Implementation.Misc; @@ -28,7 +29,8 @@ public static bool CanTranslate(Expression leftExpression) { return leftExpression is MethodCallExpression leftMethodCallExpression && - IComparableMethod.IsCompareToMethod(leftMethodCallExpression.Method); + leftMethodCallExpression.Method is var method && + (IComparableMethod.IsCompareToMethod(method) || IsStaticCompareMethod(method)); } // caller is responsible for ensuring constant is on the right @@ -39,13 +41,27 @@ public static AstFilter Translate( AstComparisonFilterOperator comparisonOperator, Expression rightExpression) { - if (leftExpression is MethodCallExpression leftMethodCallExpression && - IComparableMethod.IsCompareToMethod(leftMethodCallExpression.Method)) + if (CanTranslate(leftExpression)) { - var fieldExpression = leftMethodCallExpression.Object; + var leftMethodCallExpression = (MethodCallExpression)leftExpression; + var method= leftMethodCallExpression.Method; + var arguments = leftMethodCallExpression.Arguments; + + Expression fieldExpression; + Expression valueExpression; + if (method.IsStatic) + { + fieldExpression = arguments[0]; + valueExpression = arguments[1]; + } + else + { + fieldExpression = leftMethodCallExpression.Object; + valueExpression = arguments[0]; + } + var fieldTranslation = ExpressionToFilterFieldTranslator.Translate(context, fieldExpression); - var valueExpression = leftMethodCallExpression.Arguments[0]; var value = valueExpression.GetConstantValue(containingExpression: expression); var serializedValue = SerializationHelper.SerializeValue(fieldTranslation.Serializer, value); @@ -58,5 +74,17 @@ public static AstFilter Translate( throw new ExpressionNotSupportedException(expression); } + + private static bool IsStaticCompareMethod(MethodInfo method) + { + return + method.IsStatic && + method.IsPublic && + method.ReturnType == typeof(int) && + method.GetParameters() is var parameters && + parameters.Length == 2 && + parameters[0].ParameterType == method.DeclaringType && + parameters[1].ParameterType == parameters[0].ParameterType; + } } } diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5730Tests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5730Tests.cs new file mode 100644 index 00000000000..c0574f2bd40 --- /dev/null +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5730Tests.cs @@ -0,0 +1,92 @@ +/* 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.Collections.Generic; +using System.Linq; +using MongoDB.Driver.TestHelpers; +using FluentAssertions; +using Xunit; + +namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira; + +public class CSharp5730Tests : LinqIntegrationTest +{ + public CSharp5730Tests(ClassFixture fixture) + : base(fixture) + { + } + + [Fact] + public void Where_String_Compare_greater_than_zero_should_work() + { + var collection = Fixture.Collection; + + var queryable = collection.AsQueryable() + .Where(d => string.Compare(d.Key, "a4e48b55-0519-4ab3-b6b9-7c532fc65b56") > 0); + + var stages = Translate(collection, queryable); + AssertStages(stages, "{ $match : { Key : { $gt : 'a4e48b55-0519-4ab3-b6b9-7c532fc65b56' } } }"); + + var result = queryable.ToList(); + result.Select(x => x. Id).Should().Equal(4); + } + + [Fact] + public void Where_String_CompareTo_greater_than_zero_should_work() + { + var collection = Fixture.Collection; + + var queryable = collection.AsQueryable() + .Where(d => d.Key.CompareTo("a4e48b55-0519-4ab3-b6b9-7c532fc65b56") > 0); + + var stages = Translate(collection, queryable); + AssertStages(stages, "{ $match : { Key : { $gt : 'a4e48b55-0519-4ab3-b6b9-7c532fc65b56' } } }"); + + var result = queryable.ToList(); + result.Select(x => x. Id).Should().Equal(4); + } + + [Fact] + public void Select_String_Compare_should_work() + { + var collection = Fixture.Collection; + + var queryable = collection.AsQueryable() + .Select(d => string.Compare(d.Key, "a4e48b55-0519-4ab3-b6b9-7c532fc65b56") > 0); + + var stages = Translate(collection, queryable); + AssertStages(stages, "{ $project : { _v : { $gt : [{ $cmp : ['$Key', 'a4e48b55-0519-4ab3-b6b9-7c532fc65b56'] }, 0] }, _id : 0 } }"); + + var results = queryable.ToList(); + results.Should().Equal(false, false, false, true); + } + + public class C + { + public int Id { get; set; } + public string Key { get; set; } + } + + public sealed class ClassFixture : MongoCollectionFixture + { + protected override IEnumerable InitialData => + [ + new C { Id = 1, Key = "1b2bc240-ec2a-4a17-8790-8407e3bbb847"}, + new C { Id = 2, Key = "a4e48b55-0519-4ab3-b6b9-7c532fc65b56"}, + new C { Id = 3, Key = "9ff72c5d-189e-4511-b7ad-3f83489e4ea4"}, + new C { Id = 4, Key = "d78ca958-abac-46cd-94a7-fbf7a2ba683d"} + ]; + } +}