From fb728dcdda32a33912ed7ef485567945697aa24e Mon Sep 17 00:00:00 2001 From: rstam Date: Tue, 7 Oct 2025 15:40:29 -0400 Subject: [PATCH 1/9] CSHARP-5730: Support static String.Compare method --- .../Reflection/StringMethod.cs | 3 + ...essionToAggregationExpressionTranslator.cs | 1 + ...MethodToAggregationExpressionTranslator.cs | 43 +++++++++ ...oComparisonExpressionToFilterTranslator.cs | 38 +++++++- .../Jira/CSharp5730Tests.cs | 92 +++++++++++++++++++ 5 files changed, 172 insertions(+), 5 deletions(-) create mode 100644 src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/CompareMethodToAggregationExpressionTranslator.cs create mode 100644 tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5730Tests.cs 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"} + ]; + } +} From 3a853393412155ea54d928671f97f6cf34b794ca Mon Sep 17 00:00:00 2001 From: rstam Date: Sat, 18 Oct 2025 19:07:33 -0400 Subject: [PATCH 2/9] CSHARP-5730: Work in progress to fully support Compare. --- .../Ast/Filters/AstFilter.cs | 20 +++++ ...MethodToAggregationExpressionTranslator.cs | 5 +- ...oComparisonExpressionToFilterTranslator.cs | 18 +++- .../Jira/CSharp5730Tests.cs | 85 ++++++++++--------- 4 files changed, 85 insertions(+), 43 deletions(-) diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Filters/AstFilter.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Filters/AstFilter.cs index ab516eed243..f9c68917937 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Filters/AstFilter.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Filters/AstFilter.cs @@ -139,11 +139,31 @@ public static AstFilterField Field(string path) return new AstFilterField(path); } + public static AstFieldOperationFilter Gt(AstFilterField field, BsonValue value) + { + return new AstFieldOperationFilter(field, new AstComparisonFilterOperation(AstComparisonFilterOperator.Gt, value)); + } + + public static AstFieldOperationFilter Gte(AstFilterField field, BsonValue value) + { + return new AstFieldOperationFilter(field, new AstComparisonFilterOperation(AstComparisonFilterOperator.Gte, value)); + } + public static AstFieldOperationFilter In(AstFilterField field, IEnumerable values) { return new AstFieldOperationFilter(field, new AstInFilterOperation(values)); } + public static AstFieldOperationFilter Lt(AstFilterField field, BsonValue value) + { + return new AstFieldOperationFilter(field, new AstComparisonFilterOperation(AstComparisonFilterOperator.Lt, value)); + } + + public static AstFieldOperationFilter Lte(AstFilterField field, BsonValue value) + { + return new AstFieldOperationFilter(field, new AstComparisonFilterOperation(AstComparisonFilterOperator.Lte, value)); + } + public static AstFilter MatchesEverything() { return new AstMatchesEverythingFilter(); diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/CompareMethodToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/CompareMethodToAggregationExpressionTranslator.cs index df7a3d27b5f..5ee4d380c21 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/CompareMethodToAggregationExpressionTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/CompareMethodToAggregationExpressionTranslator.cs @@ -30,10 +30,13 @@ public static TranslatedExpression Translate(TranslationContext context, MethodC if (method.Is(StringMethod.StaticCompare)) { var strAExpression = arguments[0]; - var strATranslation = ExpressionToAggregationExpressionTranslator.Translate(context, strAExpression); var strBExpression = arguments[1]; + + var strATranslation = ExpressionToAggregationExpressionTranslator.Translate(context, strAExpression); var strBTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, strBExpression); + var ast = AstExpression.Cmp(strATranslation.Ast, strBTranslation.Ast); + return new TranslatedExpression(expression, ast, Int32Serializer.Instance); } 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 438ab7d4068..e922c23db36 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ExpressionTranslators/CompareToComparisonExpressionToFilterTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ExpressionTranslators/CompareToComparisonExpressionToFilterTranslator.cs @@ -66,10 +66,22 @@ public static AstFilter Translate( var serializedValue = SerializationHelper.SerializeValue(fieldTranslation.Serializer, value); var rightValue = rightExpression.GetConstantValue(containingExpression: expression); - if (rightValue == 0) + return (comparisonOperator, rightValue) switch { - return AstFilter.Compare(fieldTranslation.Ast, comparisonOperator, serializedValue); - } + (AstComparisonFilterOperator.Eq, -1) => AstFilter.Lt(fieldTranslation.Ast, serializedValue), + (AstComparisonFilterOperator.Ne, -1) => AstFilter.Gte(fieldTranslation.Ast, serializedValue), + (AstComparisonFilterOperator.Gt, -1) => AstFilter.Gte(fieldTranslation.Ast, serializedValue), + (AstComparisonFilterOperator.Eq, 0) => AstFilter.Eq(fieldTranslation.Ast, serializedValue), + (AstComparisonFilterOperator.Ne, 0) => AstFilter.Ne(fieldTranslation.Ast, serializedValue), + (AstComparisonFilterOperator.Lt, 0) => AstFilter.Lt(fieldTranslation.Ast, serializedValue), + (AstComparisonFilterOperator.Lte, 0) => AstFilter.Lte(fieldTranslation.Ast, serializedValue), + (AstComparisonFilterOperator.Gt, 0) => AstFilter.Gt(fieldTranslation.Ast, serializedValue), + (AstComparisonFilterOperator.Gte, 0) => AstFilter.Gte(fieldTranslation.Ast, serializedValue), + (AstComparisonFilterOperator.Eq, 1) => AstFilter.Gt(fieldTranslation.Ast, serializedValue), + (AstComparisonFilterOperator.Ne, 1) => AstFilter.Lte(fieldTranslation.Ast, serializedValue), + (AstComparisonFilterOperator.Lt, 1) => AstFilter.Lte(fieldTranslation.Ast, serializedValue), + _ => throw new ExpressionNotSupportedException(expression) + }; } throw new ExpressionNotSupportedException(expression); diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5730Tests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5730Tests.cs index c0574f2bd40..e485bea0556 100644 --- a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5730Tests.cs +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5730Tests.cs @@ -13,8 +13,10 @@ * limitations under the License. */ +using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using MongoDB.Driver.TestHelpers; using FluentAssertions; using Xunit; @@ -28,65 +30,70 @@ public CSharp5730Tests(ClassFixture fixture) { } - [Fact] - public void Where_String_Compare_greater_than_zero_should_work() + [Theory] + [InlineData( 1, "{ $match : { A : { $lt : 'B' } } }", new int[] { 1, 2 })] + [InlineData( 2, "{ $match : { A : 'B' } }", new int[] { 3 })] + [InlineData( 3, "{ $match : { A : { $gt : 'B' } } }", new int[] { 4, 5, 6 })] + [InlineData( 4, "{ $match : { A : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] + [InlineData( 5, "{ $match : { A : { $ne : 'B' } } }", new int[] { 1, 2, 4, 5, 6 })] + [InlineData( 6, "{ $match : { A : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] + [InlineData( 7, "{ $match : { A : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] + [InlineData( 8, "{ $match : { A : { $gt : 'B' } } }", new int[] { 4, 5, 6 })] + [InlineData( 9, "{ $match : { A : { $lt : 'B' } } }", new int[] { 1, 2 })] + [InlineData(10, "{ $match : { A : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] + [InlineData(11, "{ $match : { A : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] + [InlineData(12, "{ $match : { A : { $gte : 'B' } } }", new int[] { 1, 3, 4, 5, 6 })] + public void Where_String_Compare_field_to_constant_should_work(int scenario, string expectedStage, int[] expectedResults) { 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); + var queryable = scenario switch + { + // Compare field to constant + 1 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") == -1), + 2 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") == 0), + 3 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") == 1), + 4 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") != -1), + 5 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") != 0), + 6 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") != 1), + 7 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") > -1), + 8 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") > 0), + 9 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") < 0), + 10 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") < 1), + 11 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") <= 0), + 12 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") >= 0), + _ => throw new ArgumentException($"Invalid scenario: {scenario}.") + }; + + Assert(collection, queryable, expectedStage, expectedResults); } - [Fact] - public void Select_String_Compare_should_work() + private void Assert(IMongoCollection collection, IQueryable queryable, string expectedStage, int[] expectedResults) { - 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 } }"); + AssertStages(stages, expectedStage); var results = queryable.ToList(); - results.Should().Equal(false, false, false, true); + results.Select(x => x.Id).Should().Equal(expectedResults); } public class C { public int Id { get; set; } - public string Key { get; set; } + public string A { get; set; } + public string B { 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"} + new C { Id = 1, A = "A", B = "A" }, + new C { Id = 2, A = "A", B = "B" }, + new C { Id = 3, A = "B", B = "A" }, + new C { Id = 4, A = "a", B = "a" }, + new C { Id = 5, A = "a", B = "b" }, + new C { Id = 6, A = "b", B = "a" } ]; } } From f4e62c6e38d1990d89aef4437b024ee41ca73fb4 Mon Sep 17 00:00:00 2001 From: rstam Date: Mon, 20 Oct 2025 14:36:50 -0400 Subject: [PATCH 3/9] CSHARP-5730: Add support for swapping left and right sides of comparision. --- .../Filters/AstComparisonFilterOperator.cs | 14 ++ ...eComparisonExpressionToFilterTranslator.cs | 122 ++++++++++++++++++ ...oComparisonExpressionToFilterTranslator.cs | 102 --------------- .../ComparisonExpressionToFilterTranslator.cs | 24 +--- .../Jira/CSharp5730Tests.cs | 40 +++++- 5 files changed, 180 insertions(+), 122 deletions(-) create mode 100644 src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ExpressionTranslators/CompareComparisonExpressionToFilterTranslator.cs delete mode 100644 src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ExpressionTranslators/CompareToComparisonExpressionToFilterTranslator.cs diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Filters/AstComparisonFilterOperator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Filters/AstComparisonFilterOperator.cs index 8da39a25a96..50d5d75998f 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Filters/AstComparisonFilterOperator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Filters/AstComparisonFilterOperator.cs @@ -29,6 +29,20 @@ internal enum AstComparisonFilterOperator internal static class AstComparisonFilterOperatorExtensions { + public static AstComparisonFilterOperator GetComparisonOperatorForSwappedLeftAndRight(this AstComparisonFilterOperator @operator) + { + return @operator switch + { + AstComparisonFilterOperator.Eq => AstComparisonFilterOperator.Eq, + AstComparisonFilterOperator.Gt => AstComparisonFilterOperator.Lt, + AstComparisonFilterOperator.Gte => AstComparisonFilterOperator.Lte, + AstComparisonFilterOperator.Lt => AstComparisonFilterOperator.Gt, + AstComparisonFilterOperator.Lte => AstComparisonFilterOperator.Gte, + AstComparisonFilterOperator.Ne => AstComparisonFilterOperator.Ne, + _ => throw new InvalidOperationException($"Unexpected comparison filter operator: {@operator}.") + }; + } + public static string Render(this AstComparisonFilterOperator @operator) { return @operator switch diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ExpressionTranslators/CompareComparisonExpressionToFilterTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ExpressionTranslators/CompareComparisonExpressionToFilterTranslator.cs new file mode 100644 index 00000000000..0110ec5e238 --- /dev/null +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ExpressionTranslators/CompareComparisonExpressionToFilterTranslator.cs @@ -0,0 +1,122 @@ +/* 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 System.Reflection; +using MongoDB.Driver.Linq.Linq3Implementation.Ast.Filters; +using MongoDB.Driver.Linq.Linq3Implementation.ExtensionMethods; +using MongoDB.Driver.Linq.Linq3Implementation.Misc; +using MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToFilterTranslators.ToFilterFieldTranslators; + +namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToFilterTranslators.ExpressionTranslators +{ + internal static class CompareComparisonExpressionToFilterTranslator + { + public static bool CanTranslate(Expression leftExpression) + { + return + leftExpression is MethodCallExpression leftMethodCallExpression && + leftMethodCallExpression.Method is var method && + (IsStaticCompareMethod(method) || IsInstanceCompareToMethod(method)); + } + + // caller is responsible for ensuring constant is on the right + public static AstFilter Translate( + TranslationContext context, + Expression expression, + Expression leftExpression, + AstComparisonFilterOperator outerComparisonOperator, + Expression rightExpression) + { + if (CanTranslate(leftExpression)) + { + var compareMethodCallExpression = (MethodCallExpression)leftExpression; + var compareMethod = compareMethodCallExpression.Method; + var compareArguments = compareMethodCallExpression.Arguments; + var outerValue = rightExpression.GetConstantValue(containingExpression: expression); + + Expression fieldExpression; + Expression innerValueExpression; + if (compareMethod.IsStatic) + { + fieldExpression = compareArguments[0]; + innerValueExpression = compareArguments[1]; + } + else + { + fieldExpression = compareMethodCallExpression.Object; + innerValueExpression = compareArguments[0]; + } + + var fieldComparisonOperator = (outerComparisonOperator, outerValue) switch + { + (AstComparisonFilterOperator.Eq, -1) => AstComparisonFilterOperator.Lt, + (AstComparisonFilterOperator.Ne, -1) => AstComparisonFilterOperator.Gte, + (AstComparisonFilterOperator.Gt, -1) => AstComparisonFilterOperator.Gte, + (AstComparisonFilterOperator.Eq, 0) => AstComparisonFilterOperator.Eq, + (AstComparisonFilterOperator.Ne, 0) => AstComparisonFilterOperator.Ne, + (AstComparisonFilterOperator.Lt, 0) => AstComparisonFilterOperator.Lt, + (AstComparisonFilterOperator.Lte, 0) => AstComparisonFilterOperator.Lte, + (AstComparisonFilterOperator.Gt, 0) => AstComparisonFilterOperator.Gt, + (AstComparisonFilterOperator.Gte, 0) => AstComparisonFilterOperator.Gte, + (AstComparisonFilterOperator.Eq, 1) => AstComparisonFilterOperator.Gt, + (AstComparisonFilterOperator.Ne, 1) => AstComparisonFilterOperator.Lte, + (AstComparisonFilterOperator.Lt, 1) => AstComparisonFilterOperator.Lte, + _ => throw new ExpressionNotSupportedException(expression) + }; + + if (fieldExpression.NodeType == ExpressionType.Constant && innerValueExpression.NodeType != ExpressionType.Constant) + { + (fieldExpression, innerValueExpression) = (innerValueExpression, fieldExpression); + fieldComparisonOperator = fieldComparisonOperator.GetComparisonOperatorForSwappedLeftAndRight(); + } + + var fieldTranslation = ExpressionToFilterFieldTranslator.Translate(context, fieldExpression); + var value = innerValueExpression.GetConstantValue(containingExpression: expression); + var serializedValue = SerializationHelper.SerializeValue(fieldTranslation.Serializer, value); + + return AstFilter.Compare(fieldTranslation.Ast, fieldComparisonOperator, serializedValue); + } + + throw new ExpressionNotSupportedException(expression); + } + + private static bool IsInstanceCompareToMethod(MethodInfo method) + { + return + method.IsPublic && + !method.IsStatic && + method.ReturnType == typeof(int) && + method.Name == "CompareTo" && + method.GetParameters() is var parameters && + parameters.Length == 1 && + parameters[0].ParameterType is var parameterType && + (parameterType == method.DeclaringType || parameterType == typeof(object)); + } + + private static bool IsStaticCompareMethod(MethodInfo method) + { + return + method.IsPublic && + method.IsStatic && + method.ReturnType == typeof(int) && + method.Name == "Compare" && + method.GetParameters() is var parameters && + parameters.Length == 2 && + parameters[0].ParameterType == method.DeclaringType && + parameters[1].ParameterType == method.DeclaringType; + } + } +} diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ExpressionTranslators/CompareToComparisonExpressionToFilterTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ExpressionTranslators/CompareToComparisonExpressionToFilterTranslator.cs deleted file mode 100644 index e922c23db36..00000000000 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ExpressionTranslators/CompareToComparisonExpressionToFilterTranslator.cs +++ /dev/null @@ -1,102 +0,0 @@ -/* 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 System.Reflection; -using MongoDB.Driver.Linq.Linq3Implementation.Ast.Filters; -using MongoDB.Driver.Linq.Linq3Implementation.ExtensionMethods; -using MongoDB.Driver.Linq.Linq3Implementation.Misc; -using MongoDB.Driver.Linq.Linq3Implementation.Reflection; -using MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToFilterTranslators.ToFilterFieldTranslators; - -namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToFilterTranslators.ExpressionTranslators -{ - internal static class CompareToComparisonExpressionToFilterTranslator - { - public static bool CanTranslate(Expression leftExpression) - { - return - leftExpression is MethodCallExpression leftMethodCallExpression && - leftMethodCallExpression.Method is var method && - (IComparableMethod.IsCompareToMethod(method) || IsStaticCompareMethod(method)); - } - - // caller is responsible for ensuring constant is on the right - public static AstFilter Translate( - TranslationContext context, - Expression expression, - Expression leftExpression, - AstComparisonFilterOperator comparisonOperator, - Expression rightExpression) - { - if (CanTranslate(leftExpression)) - { - 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 value = valueExpression.GetConstantValue(containingExpression: expression); - var serializedValue = SerializationHelper.SerializeValue(fieldTranslation.Serializer, value); - - var rightValue = rightExpression.GetConstantValue(containingExpression: expression); - return (comparisonOperator, rightValue) switch - { - (AstComparisonFilterOperator.Eq, -1) => AstFilter.Lt(fieldTranslation.Ast, serializedValue), - (AstComparisonFilterOperator.Ne, -1) => AstFilter.Gte(fieldTranslation.Ast, serializedValue), - (AstComparisonFilterOperator.Gt, -1) => AstFilter.Gte(fieldTranslation.Ast, serializedValue), - (AstComparisonFilterOperator.Eq, 0) => AstFilter.Eq(fieldTranslation.Ast, serializedValue), - (AstComparisonFilterOperator.Ne, 0) => AstFilter.Ne(fieldTranslation.Ast, serializedValue), - (AstComparisonFilterOperator.Lt, 0) => AstFilter.Lt(fieldTranslation.Ast, serializedValue), - (AstComparisonFilterOperator.Lte, 0) => AstFilter.Lte(fieldTranslation.Ast, serializedValue), - (AstComparisonFilterOperator.Gt, 0) => AstFilter.Gt(fieldTranslation.Ast, serializedValue), - (AstComparisonFilterOperator.Gte, 0) => AstFilter.Gte(fieldTranslation.Ast, serializedValue), - (AstComparisonFilterOperator.Eq, 1) => AstFilter.Gt(fieldTranslation.Ast, serializedValue), - (AstComparisonFilterOperator.Ne, 1) => AstFilter.Lte(fieldTranslation.Ast, serializedValue), - (AstComparisonFilterOperator.Lt, 1) => AstFilter.Lte(fieldTranslation.Ast, serializedValue), - _ => throw new ExpressionNotSupportedException(expression) - }; - } - - 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/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ExpressionTranslators/ComparisonExpressionToFilterTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ExpressionTranslators/ComparisonExpressionToFilterTranslator.cs index 46e6c9c459e..a782bbb2f88 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ExpressionTranslators/ComparisonExpressionToFilterTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ExpressionTranslators/ComparisonExpressionToFilterTranslator.cs @@ -26,13 +26,13 @@ internal static class ComparisonExpressionToFilterTranslator { public static AstFilter Translate(TranslationContext context, BinaryExpression expression) { - var comparisonOperator = GetComparisonOperator(expression); + var comparisonOperator = GetAstComparisonOperator(expression); var leftExpression = expression.Left; var rightExpression = expression.Right; if (leftExpression.NodeType == ExpressionType.Constant && rightExpression.NodeType != ExpressionType.Constant) { - comparisonOperator = GetComparisonOperatorForSwappedLeftAndRight(expression); + comparisonOperator = comparisonOperator.GetComparisonOperatorForSwappedLeftAndRight(); (leftExpression, rightExpression) = (rightExpression, leftExpression); } @@ -46,9 +46,9 @@ public static AstFilter Translate(TranslationContext context, BinaryExpression e return BitMaskComparisonExpressionToFilterTranslator.Translate(context, expression, leftExpression, comparisonOperator, rightExpression); } - if (CompareToComparisonExpressionToFilterTranslator.CanTranslate(leftExpression)) + if (CompareComparisonExpressionToFilterTranslator.CanTranslate(leftExpression)) { - return CompareToComparisonExpressionToFilterTranslator.Translate(context, expression, leftExpression, comparisonOperator, rightExpression); + return CompareComparisonExpressionToFilterTranslator.Translate(context, expression, leftExpression, comparisonOperator, rightExpression); } if (CountComparisonExpressionToFilterTranslator.CanTranslate(leftExpression, rightExpression, out var countExpression, out sizeExpression)) @@ -129,7 +129,7 @@ fieldOperationFilter.Operation is AstComparisonFilterOperation comparisonOperati } } - private static AstComparisonFilterOperator GetComparisonOperator(Expression expression) + private static AstComparisonFilterOperator GetAstComparisonOperator(Expression expression) { switch (expression.NodeType) { @@ -142,19 +142,5 @@ private static AstComparisonFilterOperator GetComparisonOperator(Expression expr default: throw new ExpressionNotSupportedException(expression); } } - - private static AstComparisonFilterOperator GetComparisonOperatorForSwappedLeftAndRight(Expression expression) - { - switch (expression.NodeType) - { - case ExpressionType.Equal: return AstComparisonFilterOperator.Eq; - case ExpressionType.GreaterThan: return AstComparisonFilterOperator.Lt; - case ExpressionType.GreaterThanOrEqual: return AstComparisonFilterOperator.Lte; - case ExpressionType.LessThan: return AstComparisonFilterOperator.Gt; - case ExpressionType.LessThanOrEqual: return AstComparisonFilterOperator.Gte; - case ExpressionType.NotEqual: return AstComparisonFilterOperator.Ne; - default: throw new ExpressionNotSupportedException(expression); - } - } } } diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5730Tests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5730Tests.cs index e485bea0556..25765cbeb59 100644 --- a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5730Tests.cs +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5730Tests.cs @@ -42,7 +42,7 @@ public CSharp5730Tests(ClassFixture fixture) [InlineData( 9, "{ $match : { A : { $lt : 'B' } } }", new int[] { 1, 2 })] [InlineData(10, "{ $match : { A : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] [InlineData(11, "{ $match : { A : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] - [InlineData(12, "{ $match : { A : { $gte : 'B' } } }", new int[] { 1, 3, 4, 5, 6 })] + [InlineData(12, "{ $match : { A : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] public void Where_String_Compare_field_to_constant_should_work(int scenario, string expectedStage, int[] expectedResults) { var collection = Fixture.Collection; @@ -68,6 +68,44 @@ public void Where_String_Compare_field_to_constant_should_work(int scenario, str Assert(collection, queryable, expectedStage, expectedResults); } + [Theory] + [InlineData( 1, "{ $match : { B : { $gt : 'A' } } }", new int[] { 2, 4, 5, 6 })] + [InlineData( 2, "{ $match : { B : 'A' } }", new int[] { 1, 3 })] + [InlineData( 3, "{ $match : { B : { $lt : 'A' } } }", new int[] { })] + [InlineData( 4, "{ $match : { B : { $lte : 'A' } } }", new int[] { 1, 3 })] + [InlineData( 5, "{ $match : { B : { $ne : 'A' } } }", new int[] { 2, 4, 5, 6 })] + [InlineData( 6, "{ $match : { B : { $gte : 'A' } } }", new int[] { 1, 2, 3, 4, 5, 6 })] + [InlineData( 7, "{ $match : { B : { $lte : 'A' } } }", new int[] { 1, 3 })] + [InlineData( 8, "{ $match : { B : { $lt : 'A' } } }", new int[] { })] + [InlineData( 9, "{ $match : { B : { $gt : 'A' } } }", new int[] { 2, 4, 5, 6 })] + [InlineData(10, "{ $match : { B : { $gte : 'A' } } }", new int[] { 1, 2, 3, 4, 5, 6 })] + [InlineData(11, "{ $match : { B : { $gte : 'A' } } }", new int[] { 1, 2, 3, 4, 5, 6 })] + [InlineData(12, "{ $match : { B : { $lte : 'A' } } }", new int[] { 1, 3 })] + public void Where_String_Compare_constant_to_field_should_work(int scenario, string expectedStage, int[] expectedResults) + { + var collection = Fixture.Collection; + + var queryable = scenario switch + { + // Compare field to constant + 1 => collection.AsQueryable().Where(x => string.Compare ("A", x.B) == -1), + 2 => collection.AsQueryable().Where(x => string.Compare ("A", x.B) == 0), + 3 => collection.AsQueryable().Where(x => string.Compare ("A", x.B) == 1), + 4 => collection.AsQueryable().Where(x => string.Compare ("A", x.B) != -1), + 5 => collection.AsQueryable().Where(x => string.Compare ("A", x.B) != 0), + 6 => collection.AsQueryable().Where(x => string.Compare ("A", x.B) != 1), + 7 => collection.AsQueryable().Where(x => string.Compare ("A", x.B) > -1), + 8 => collection.AsQueryable().Where(x => string.Compare ("A", x.B) > 0), + 9 => collection.AsQueryable().Where(x => string.Compare ("A", x.B) < 0), + 10 => collection.AsQueryable().Where(x => string.Compare("A", x.B) < 1), + 11 => collection.AsQueryable().Where(x => string.Compare("A", x.B) <= 0), + 12 => collection.AsQueryable().Where(x => string.Compare("A", x.B) >= 0), + _ => throw new ArgumentException($"Invalid scenario: {scenario}.") + }; + + Assert(collection, queryable, expectedStage, expectedResults); + } + private void Assert(IMongoCollection collection, IQueryable queryable, string expectedStage, int[] expectedResults) { var stages = Translate(collection, queryable); From eea6c7464908f464942ea11b0c54fdd9132eea94 Mon Sep 17 00:00:00 2001 From: rstam Date: Mon, 20 Oct 2025 17:27:17 -0400 Subject: [PATCH 4/9] CSHARP-5730: Added new tests for Select. --- .../Jira/CSharp5730Tests.cs | 95 ++++++++++++++++++- 1 file changed, 90 insertions(+), 5 deletions(-) diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5730Tests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5730Tests.cs index 25765cbeb59..0135a731ee9 100644 --- a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5730Tests.cs +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5730Tests.cs @@ -43,7 +43,7 @@ public CSharp5730Tests(ClassFixture fixture) [InlineData(10, "{ $match : { A : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] [InlineData(11, "{ $match : { A : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] [InlineData(12, "{ $match : { A : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] - public void Where_String_Compare_field_to_constant_should_work(int scenario, string expectedStage, int[] expectedResults) + public void Where_String_Compare_field_to_constant_should_work(int scenario, string expectedStage, int[] expectedIds) { var collection = Fixture.Collection; @@ -65,7 +65,7 @@ public void Where_String_Compare_field_to_constant_should_work(int scenario, str _ => throw new ArgumentException($"Invalid scenario: {scenario}.") }; - Assert(collection, queryable, expectedStage, expectedResults); + AssertIds(collection, queryable, expectedStage, expectedIds); } [Theory] @@ -81,7 +81,7 @@ public void Where_String_Compare_field_to_constant_should_work(int scenario, str [InlineData(10, "{ $match : { B : { $gte : 'A' } } }", new int[] { 1, 2, 3, 4, 5, 6 })] [InlineData(11, "{ $match : { B : { $gte : 'A' } } }", new int[] { 1, 2, 3, 4, 5, 6 })] [InlineData(12, "{ $match : { B : { $lte : 'A' } } }", new int[] { 1, 3 })] - public void Where_String_Compare_constant_to_field_should_work(int scenario, string expectedStage, int[] expectedResults) + public void Where_String_Compare_constant_to_field_should_work(int scenario, string expectedStage, int[] expectedIds) { var collection = Fixture.Collection; @@ -103,16 +103,101 @@ public void Where_String_Compare_constant_to_field_should_work(int scenario, str _ => throw new ArgumentException($"Invalid scenario: {scenario}.") }; + AssertIds(collection, queryable, expectedStage, expectedIds); + } + + [Theory] + [InlineData( 1, "{ $project : { _v : { $eq : [{ $cmp : ['$A', 'B'] }, -1] }, _id : 0 } }", new bool[] { true, true, false, false, false, false })] + [InlineData( 2, "{ $project : { _v : { $eq : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, false, false, false })] + [InlineData( 3, "{ $project : { _v : { $eq : [{ $cmp : ['$A', 'B'] }, 1] }, _id : 0 } }", new bool[] { false, false, false, true, true, true })] + [InlineData( 4, "{ $project : { _v : { $ne : [{ $cmp : ['$A', 'B'] }, -1] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] + [InlineData( 5, "{ $project : { _v : { $ne : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, true, true, true })] + [InlineData( 6, "{ $project : { _v : { $ne : [{ $cmp : ['$A', 'B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] + [InlineData( 7, "{ $project : { _v : { $gt : [{ $cmp : ['$A', 'B'] }, -1] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] + [InlineData( 8, "{ $project : { _v : { $gt : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, false, true, true, true })] + [InlineData( 9, "{ $project : { _v : { $lt : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, false, false, false })] + [InlineData( 10, "{ $project : { _v : { $lt : [{ $cmp : ['$A', 'B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] + [InlineData( 11, "{ $project : { _v : { $lte : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] + [InlineData( 12, "{ $project : { _v : { $gte : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] + public void Select_String_Compare_field_to_constant_should_work(int scenario, string expectedStage, bool[] expectedResults) + { + var collection = Fixture.Collection; + + var queryable = scenario switch + { + // Compare field to constant + 1 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") == -1), + 2 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") == 0), + 3 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") == 1), + 4 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") != -1), + 5 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") != 0), + 6 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") != 1), + 7 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") > -1), + 8 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") > 0), + 9 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") < 0), + 10 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") < 1), + 11 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") <= 0), + 12 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") >= 0), + _ => throw new ArgumentException($"Invalid scenario: {scenario}.") + }; + Assert(collection, queryable, expectedStage, expectedResults); } - private void Assert(IMongoCollection collection, IQueryable queryable, string expectedStage, int[] expectedResults) + [Theory] + [InlineData( 1, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] + [InlineData( 2, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] + [InlineData( 3, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { false, false, false, false, false, false })] + [InlineData( 4, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] + [InlineData( 5, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] + [InlineData( 6, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] + [InlineData( 7, "{ $project : { _v : { $gt : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] + [InlineData( 8, "{ $project : { _v : { $gt : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, false, false, false, false, false })] + [InlineData( 9, "{ $project : { _v : { $lt : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] + [InlineData( 10, "{ $project : { _v : { $lt : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] + [InlineData( 11, "{ $project : { _v : { $lte : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] + [InlineData( 12, "{ $project : { _v : { $gte : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] + public void Select_String_Compare_constant_to_field_should_work(int scenario, string expectedStage, bool[] expectedResults) + { + var collection = Fixture.Collection; + + var queryable = scenario switch + { + // Compare field to constant + 1 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) == -1), + 2 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) == 0), + 3 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) == 1), + 4 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) != -1), + 5 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) != 0), + 6 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) != 1), + 7 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) > -1), + 8 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) > 0), + 9 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) < 0), + 10 => collection.AsQueryable().Select(x => string.Compare("A", x.B) < 1), + 11 => collection.AsQueryable().Select(x => string.Compare("A", x.B) <= 0), + 12 => collection.AsQueryable().Select(x => string.Compare("A", x.B) >= 0), + _ => throw new ArgumentException($"Invalid scenario: {scenario}.") + }; + + Assert(collection, queryable, expectedStage, expectedResults); + } + + private void Assert(IMongoCollection collection, IQueryable queryable, string expectedStage, TResult[] expectedResults) + { + var stages = Translate(collection, queryable); + AssertStages(stages, expectedStage); + + var results = queryable.ToList(); + results.Should().Equal(expectedResults); + } + + private void AssertIds(IMongoCollection collection, IQueryable queryable, string expectedStage, int[] expectedIds) { var stages = Translate(collection, queryable); AssertStages(stages, expectedStage); var results = queryable.ToList(); - results.Select(x => x.Id).Should().Equal(expectedResults); + results.Select(x => x.Id).Should().Equal(expectedIds); } public class C From 3852a304eb9d906146127804e96caf9e9bd92340 Mon Sep 17 00:00:00 2001 From: rstam Date: Tue, 21 Oct 2025 09:49:36 -0400 Subject: [PATCH 5/9] CSHARP-5730: Added tests for Select Compare field to field. --- .../Jira/CSharp5730Tests.cs | 233 ++++++++++++------ 1 file changed, 154 insertions(+), 79 deletions(-) diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5730Tests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5730Tests.cs index 0135a731ee9..6461011754c 100644 --- a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5730Tests.cs +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5730Tests.cs @@ -16,7 +16,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; using MongoDB.Driver.TestHelpers; using FluentAssertions; using Xunit; @@ -31,41 +30,117 @@ public CSharp5730Tests(ClassFixture fixture) } [Theory] - [InlineData( 1, "{ $match : { A : { $lt : 'B' } } }", new int[] { 1, 2 })] - [InlineData( 2, "{ $match : { A : 'B' } }", new int[] { 3 })] - [InlineData( 3, "{ $match : { A : { $gt : 'B' } } }", new int[] { 4, 5, 6 })] - [InlineData( 4, "{ $match : { A : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] - [InlineData( 5, "{ $match : { A : { $ne : 'B' } } }", new int[] { 1, 2, 4, 5, 6 })] - [InlineData( 6, "{ $match : { A : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] - [InlineData( 7, "{ $match : { A : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] - [InlineData( 8, "{ $match : { A : { $gt : 'B' } } }", new int[] { 4, 5, 6 })] - [InlineData( 9, "{ $match : { A : { $lt : 'B' } } }", new int[] { 1, 2 })] - [InlineData(10, "{ $match : { A : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] - [InlineData(11, "{ $match : { A : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] - [InlineData(12, "{ $match : { A : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] - public void Where_String_Compare_field_to_constant_should_work(int scenario, string expectedStage, int[] expectedIds) + [InlineData( 1, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] + [InlineData( 2, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] + [InlineData( 3, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { false, false, false, false, false, false })] + [InlineData( 4, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] + [InlineData( 5, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] + [InlineData( 6, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] + [InlineData( 7, "{ $project : { _v : { $gt : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] + [InlineData( 8, "{ $project : { _v : { $gt : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, false, false, false, false, false })] + [InlineData( 9, "{ $project : { _v : { $lt : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] + [InlineData( 10, "{ $project : { _v : { $lt : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] + [InlineData( 11, "{ $project : { _v : { $lte : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] + [InlineData( 12, "{ $project : { _v : { $gte : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] + public void Select_String_Compare_constant_to_field_should_work(int scenario, string expectedStage, bool[] expectedResults) { var collection = Fixture.Collection; var queryable = scenario switch { // Compare field to constant - 1 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") == -1), - 2 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") == 0), - 3 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") == 1), - 4 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") != -1), - 5 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") != 0), - 6 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") != 1), - 7 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") > -1), - 8 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") > 0), - 9 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") < 0), - 10 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") < 1), - 11 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") <= 0), - 12 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") >= 0), + 1 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) == -1), + 2 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) == 0), + 3 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) == 1), + 4 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) != -1), + 5 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) != 0), + 6 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) != 1), + 7 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) > -1), + 8 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) > 0), + 9 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) < 0), + 10 => collection.AsQueryable().Select(x => string.Compare("A", x.B) < 1), + 11 => collection.AsQueryable().Select(x => string.Compare("A", x.B) <= 0), + 12 => collection.AsQueryable().Select(x => string.Compare("A", x.B) >= 0), _ => throw new ArgumentException($"Invalid scenario: {scenario}.") }; - AssertIds(collection, queryable, expectedStage, expectedIds); + Assert(collection, queryable, expectedStage, expectedResults); + } + + [Theory] + [InlineData( 1, "{ $project : { _v : { $eq : [{ $cmp : ['$A', 'B'] }, -1] }, _id : 0 } }", new bool[] { true, true, false, false, false, false })] + [InlineData( 2, "{ $project : { _v : { $eq : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, false, false, false })] + [InlineData( 3, "{ $project : { _v : { $eq : [{ $cmp : ['$A', 'B'] }, 1] }, _id : 0 } }", new bool[] { false, false, false, true, true, true })] + [InlineData( 4, "{ $project : { _v : { $ne : [{ $cmp : ['$A', 'B'] }, -1] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] + [InlineData( 5, "{ $project : { _v : { $ne : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, true, true, true })] + [InlineData( 6, "{ $project : { _v : { $ne : [{ $cmp : ['$A', 'B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] + [InlineData( 7, "{ $project : { _v : { $gt : [{ $cmp : ['$A', 'B'] }, -1] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] + [InlineData( 8, "{ $project : { _v : { $gt : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, false, true, true, true })] + [InlineData( 9, "{ $project : { _v : { $lt : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, false, false, false })] + [InlineData( 10, "{ $project : { _v : { $lt : [{ $cmp : ['$A', 'B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] + [InlineData( 11, "{ $project : { _v : { $lte : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] + [InlineData( 12, "{ $project : { _v : { $gte : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] + public void Select_String_Compare_field_to_constant_should_work(int scenario, string expectedStage, bool[] expectedResults) + { + var collection = Fixture.Collection; + + var queryable = scenario switch + { + // Compare field to constant + 1 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") == -1), + 2 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") == 0), + 3 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") == 1), + 4 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") != -1), + 5 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") != 0), + 6 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") != 1), + 7 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") > -1), + 8 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") > 0), + 9 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") < 0), + 10 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") < 1), + 11 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") <= 0), + 12 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") >= 0), + _ => throw new ArgumentException($"Invalid scenario: {scenario}.") + }; + + Assert(collection, queryable, expectedStage, expectedResults); + } + + [Theory] + [InlineData( 1, "{ $project : { _v : { $eq : [{ $cmp : ['$A', '$B'] }, -1] }, _id : 0 } }", new bool[] { false, true, false, false, true, false })] + [InlineData( 2, "{ $project : { _v : { $eq : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, false, true, false, false })] + [InlineData( 3, "{ $project : { _v : { $eq : [{ $cmp : ['$A', '$B'] }, 1] }, _id : 0 } }", new bool[] { false, false, true, false, false, true })] + [InlineData( 4, "{ $project : { _v : { $ne : [{ $cmp : ['$A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] + [InlineData( 5, "{ $project : { _v : { $ne : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, true, false, true, true })] + [InlineData( 6, "{ $project : { _v : { $ne : [{ $cmp : ['$A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] + [InlineData( 7, "{ $project : { _v : { $gt : [{ $cmp : ['$A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] + [InlineData( 8, "{ $project : { _v : { $gt : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, false, false, true })] + [InlineData( 9, "{ $project : { _v : { $lt : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, false, true, false })] + [InlineData( 10, "{ $project : { _v : { $lt : [{ $cmp : ['$A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] + [InlineData( 11, "{ $project : { _v : { $lte : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] + [InlineData( 12, "{ $project : { _v : { $gte : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] + public void Select_String_Compare_field_to_field_should_work(int scenario, string expectedStage, bool[] expectedResults) + { + var collection = Fixture.Collection; + + var queryable = scenario switch + { + // Compare field to constant + 1 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B) == -1), + 2 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B) == 0), + 3 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B) == 1), + 4 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B) != -1), + 5 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B) != 0), + 6 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B) != 1), + 7 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B) > -1), + 8 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B) > 0), + 9 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B) < 0), + 10 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B) < 1), + 11 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B) <= 0), + 12 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B) >= 0), + _ => throw new ArgumentException($"Invalid scenario: {scenario}.") + }; + + Assert(collection, queryable, expectedStage, expectedResults); } [Theory] @@ -107,79 +182,79 @@ public void Where_String_Compare_constant_to_field_should_work(int scenario, str } [Theory] - [InlineData( 1, "{ $project : { _v : { $eq : [{ $cmp : ['$A', 'B'] }, -1] }, _id : 0 } }", new bool[] { true, true, false, false, false, false })] - [InlineData( 2, "{ $project : { _v : { $eq : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, false, false, false })] - [InlineData( 3, "{ $project : { _v : { $eq : [{ $cmp : ['$A', 'B'] }, 1] }, _id : 0 } }", new bool[] { false, false, false, true, true, true })] - [InlineData( 4, "{ $project : { _v : { $ne : [{ $cmp : ['$A', 'B'] }, -1] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] - [InlineData( 5, "{ $project : { _v : { $ne : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, true, true, true })] - [InlineData( 6, "{ $project : { _v : { $ne : [{ $cmp : ['$A', 'B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] - [InlineData( 7, "{ $project : { _v : { $gt : [{ $cmp : ['$A', 'B'] }, -1] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] - [InlineData( 8, "{ $project : { _v : { $gt : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, false, true, true, true })] - [InlineData( 9, "{ $project : { _v : { $lt : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, false, false, false })] - [InlineData( 10, "{ $project : { _v : { $lt : [{ $cmp : ['$A', 'B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] - [InlineData( 11, "{ $project : { _v : { $lte : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] - [InlineData( 12, "{ $project : { _v : { $gte : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] - public void Select_String_Compare_field_to_constant_should_work(int scenario, string expectedStage, bool[] expectedResults) + [InlineData( 1, "{ $match : { A : { $lt : 'B' } } }", new int[] { 1, 2 })] + [InlineData( 2, "{ $match : { A : 'B' } }", new int[] { 3 })] + [InlineData( 3, "{ $match : { A : { $gt : 'B' } } }", new int[] { 4, 5, 6 })] + [InlineData( 4, "{ $match : { A : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] + [InlineData( 5, "{ $match : { A : { $ne : 'B' } } }", new int[] { 1, 2, 4, 5, 6 })] + [InlineData( 6, "{ $match : { A : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] + [InlineData( 7, "{ $match : { A : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] + [InlineData( 8, "{ $match : { A : { $gt : 'B' } } }", new int[] { 4, 5, 6 })] + [InlineData( 9, "{ $match : { A : { $lt : 'B' } } }", new int[] { 1, 2 })] + [InlineData(10, "{ $match : { A : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] + [InlineData(11, "{ $match : { A : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] + [InlineData(12, "{ $match : { A : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] + public void Where_String_Compare_field_to_constant_should_work(int scenario, string expectedStage, int[] expectedIds) { var collection = Fixture.Collection; var queryable = scenario switch { // Compare field to constant - 1 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") == -1), - 2 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") == 0), - 3 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") == 1), - 4 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") != -1), - 5 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") != 0), - 6 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") != 1), - 7 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") > -1), - 8 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") > 0), - 9 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") < 0), - 10 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") < 1), - 11 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") <= 0), - 12 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") >= 0), + 1 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") == -1), + 2 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") == 0), + 3 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") == 1), + 4 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") != -1), + 5 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") != 0), + 6 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") != 1), + 7 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") > -1), + 8 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") > 0), + 9 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") < 0), + 10 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") < 1), + 11 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") <= 0), + 12 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") >= 0), _ => throw new ArgumentException($"Invalid scenario: {scenario}.") }; - Assert(collection, queryable, expectedStage, expectedResults); + AssertIds(collection, queryable, expectedStage, expectedIds); } [Theory] - [InlineData( 1, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] - [InlineData( 2, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] - [InlineData( 3, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { false, false, false, false, false, false })] - [InlineData( 4, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] - [InlineData( 5, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] - [InlineData( 6, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] - [InlineData( 7, "{ $project : { _v : { $gt : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] - [InlineData( 8, "{ $project : { _v : { $gt : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, false, false, false, false, false })] - [InlineData( 9, "{ $project : { _v : { $lt : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] - [InlineData( 10, "{ $project : { _v : { $lt : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] - [InlineData( 11, "{ $project : { _v : { $lte : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] - [InlineData( 12, "{ $project : { _v : { $gte : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] - public void Select_String_Compare_constant_to_field_should_work(int scenario, string expectedStage, bool[] expectedResults) + [InlineData( 1, "{ $match : { $expr : { $eq : [{ $cmp : ['$A', '$B'] }, -1] } } }", new int[] { 2, 5 })] + [InlineData( 2, "{ $match : { $expr : { $eq : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 1, 4 })] + [InlineData( 3, "{ $match : { $expr : { $eq : [{ $cmp : ['$A', '$B'] }, 1] } } }", new int[] { 3, 6 })] + [InlineData( 4, "{ $match : { $expr : { $ne : [{ $cmp : ['$A', '$B'] }, -1] } } }", new int[] { 1, 3, 4, 6 })] + [InlineData( 5, "{ $match : { $expr : { $ne : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 2, 3, 5, 6 })] + [InlineData( 6, "{ $match : { $expr : { $ne : [{ $cmp : ['$A', '$B'] }, 1] } } }", new int[] { 1, 2, 4, 5 })] + [InlineData( 7, "{ $match : { $expr : { $gt : [{ $cmp : ['$A', '$B'] }, -1] } } }", new int[] { 1, 3, 4, 6 })] + [InlineData( 8, "{ $match : { $expr : { $gt : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 3, 6 })] + [InlineData( 9, "{ $match : { $expr : { $lt : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 2, 5 })] + [InlineData( 10, "{ $match : { $expr : { $lt : [{ $cmp : ['$A', '$B'] }, 1] } } }", new int[] { 1, 2, 4, 5 })] + [InlineData( 11, "{ $match : { $expr : { $lte : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 1, 2, 4, 5 })] + [InlineData( 12, "{ $match : { $expr : { $gte : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 1, 3, 4, 6 })] + public void Where_String_Compare_field_to_field_should_work(int scenario, string expectedStage, int[] expectedIds) { var collection = Fixture.Collection; var queryable = scenario switch { // Compare field to constant - 1 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) == -1), - 2 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) == 0), - 3 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) == 1), - 4 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) != -1), - 5 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) != 0), - 6 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) != 1), - 7 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) > -1), - 8 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) > 0), - 9 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) < 0), - 10 => collection.AsQueryable().Select(x => string.Compare("A", x.B) < 1), - 11 => collection.AsQueryable().Select(x => string.Compare("A", x.B) <= 0), - 12 => collection.AsQueryable().Select(x => string.Compare("A", x.B) >= 0), + 1 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B) == -1), + 2 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B) == 0), + 3 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B) == 1), + 4 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B) != -1), + 5 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B) != 0), + 6 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B) != 1), + 7 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B) > -1), + 8 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B) > 0), + 9 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B) < 0), + 10 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B) < 1), + 11 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B) <= 0), + 12 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B) >= 0), _ => throw new ArgumentException($"Invalid scenario: {scenario}.") }; - Assert(collection, queryable, expectedStage, expectedResults); + AssertIds(collection, queryable, expectedStage, expectedIds); } private void Assert(IMongoCollection collection, IQueryable queryable, string expectedStage, TResult[] expectedResults) From d34f3d3cd978a98d30642e1a74ba092f80ab141d Mon Sep 17 00:00:00 2001 From: rstam Date: Tue, 21 Oct 2025 15:18:22 -0400 Subject: [PATCH 6/9] CSHARP-5730: Added support for Compare with ignoreCase overloads. --- .../Misc/MethodInfoExtensions.cs | 26 + .../Reflection/StringMethod.cs | 3 + ...essionToAggregationExpressionTranslator.cs | 6 +- ...MethodToAggregationExpressionTranslator.cs | 42 +- ...MethodToAggregationExpressionTranslator.cs | 43 -- ...eComparisonExpressionToFilterTranslator.cs | 38 +- .../Jira/CSharp5730Tests.cs | 468 ++++++++++++++---- 7 files changed, 464 insertions(+), 162 deletions(-) delete mode 100644 src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/CompareToMethodToAggregationExpressionTranslator.cs diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/MethodInfoExtensions.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/MethodInfoExtensions.cs index d7a61ab07d5..7059f574cea 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/MethodInfoExtensions.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/MethodInfoExtensions.cs @@ -38,6 +38,19 @@ public static bool Is(this MethodInfo method, MethodInfo comparand) return false; } + public static bool IsInstanceCompareToMethod(this MethodInfo method) + { + return + method.IsPublic && + !method.IsStatic && + method.ReturnType == typeof(int) && + method.Name == "CompareTo" && + method.GetParameters() is var parameters && + parameters.Length == 1 && + parameters[0].ParameterType is var parameterType && + (parameterType == method.DeclaringType || parameterType == typeof(object)); + } + public static bool IsOneOf(this MethodInfo method, MethodInfo comparand1, MethodInfo comparand2) { return method.Is(comparand1) || method.Is(comparand2); @@ -78,5 +91,18 @@ public static bool IsOneOf(this MethodInfo method, params MethodInfo[][] compara return false; } + + public static bool IsStaticCompareMethod(this MethodInfo method) + { + return + method.IsPublic && + method.IsStatic && + method.ReturnType == typeof(int) && + method.Name == "Compare" && + method.GetParameters() is var parameters && + parameters.Length == 2 && + parameters[0].ParameterType == method.DeclaringType && + parameters[1].ParameterType == method.DeclaringType; + } } } diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Reflection/StringMethod.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Reflection/StringMethod.cs index 41ef1422449..96177eab363 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Reflection/StringMethod.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Reflection/StringMethod.cs @@ -73,6 +73,7 @@ internal static class StringMethod private static readonly MethodInfo __startsWithWithStringAndComparisonType; private static readonly MethodInfo __startsWithWithStringAndIgnoreCaseAndCulture; private static readonly MethodInfo __staticCompare; + private static readonly MethodInfo __staticCompareWithIgnoreCase; private static readonly MethodInfo __stringInWithEnumerable; private static readonly MethodInfo __stringInWithParams; private static readonly MethodInfo __stringNinWithEnumerable; @@ -153,6 +154,7 @@ static StringMethod() __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)); + __staticCompareWithIgnoreCase = ReflectionInfo.Method((string strA, string strB, bool ignoreCase) => String.Compare(strA, strB, ignoreCase)); __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)); @@ -223,6 +225,7 @@ static StringMethod() public static MethodInfo StartsWithWithStringAndComparisonType => __startsWithWithStringAndComparisonType; public static MethodInfo StartsWithWithStringAndIgnoreCaseAndCulture => __startsWithWithStringAndIgnoreCaseAndCulture; public static MethodInfo StaticCompare => __staticCompare; + public static MethodInfo StaticCompareWithIgnoreCase => __staticCompareWithIgnoreCase; 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 14942e6b793..a12a47cfc8b 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodCallExpressionToAggregationExpressionTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodCallExpressionToAggregationExpressionTranslator.cs @@ -33,8 +33,6 @@ 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); case "Contains": return ContainsMethodToAggregationExpressionTranslator.Translate(context, expression); @@ -139,6 +137,10 @@ public static TranslatedExpression Translate(TranslationContext context, MethodC case "TopN": return PickMethodToAggregationExpressionTranslator.Translate(context, expression); + case "Compare": + case "CompareTo": + return CompareMethodToAggregationExpressionTranslator.Translate(context, expression); + case "Count": case "LongCount": return CountMethodToAggregationExpressionTranslator.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 index 5ee4d380c21..79f68c311ce 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/CompareMethodToAggregationExpressionTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/CompareMethodToAggregationExpressionTranslator.cs @@ -14,28 +14,58 @@ */ using System.Linq.Expressions; +using System.Reflection; using MongoDB.Bson.Serialization.Serializers; using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions; +using MongoDB.Driver.Linq.Linq3Implementation.ExtensionMethods; using MongoDB.Driver.Linq.Linq3Implementation.Misc; namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.MethodTranslators { internal static class CompareMethodToAggregationExpressionTranslator { + private static readonly MethodInfo[] __stringCompareMethods = + [ + StringMethod.StaticCompare, + StringMethod.StaticCompareWithIgnoreCase + ]; + public static TranslatedExpression Translate(TranslationContext context, MethodCallExpression expression) { var method = expression.Method; var arguments = expression.Arguments; - if (method.Is(StringMethod.StaticCompare)) + if (method.IsStaticCompareMethod() || method.IsInstanceCompareToMethod() || method.IsOneOf(__stringCompareMethods)) { - var strAExpression = arguments[0]; - var strBExpression = arguments[1]; + Expression value1Expression; + Expression value2Expression; + if (method.IsStatic) + { + value1Expression = arguments[0]; + value2Expression = arguments[1]; + } + else + { + value1Expression = expression.Object; + value2Expression = arguments[0]; + } - var strATranslation = ExpressionToAggregationExpressionTranslator.Translate(context, strAExpression); - var strBTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, strBExpression); + var value1Translation = ExpressionToAggregationExpressionTranslator.Translate(context, value1Expression); + var value2Translation = ExpressionToAggregationExpressionTranslator.Translate(context, value2Expression); - var ast = AstExpression.Cmp(strATranslation.Ast, strBTranslation.Ast); + AstExpression ast; + if (method.Is(StringMethod.StaticCompareWithIgnoreCase)) + { + var ignoreCaseExpression = arguments[2]; + var ignoreCase = ignoreCaseExpression.GetConstantValue(containingExpression: expression); + ast = ignoreCase + ? AstExpression.StrCaseCmp(value1Translation.Ast, value2Translation.Ast) + : AstExpression.Cmp(value1Translation.Ast, value2Translation.Ast); + } + else + { + ast = AstExpression.Cmp(value1Translation.Ast, value2Translation.Ast); + } return new TranslatedExpression(expression, ast, Int32Serializer.Instance); } diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/CompareToMethodToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/CompareToMethodToAggregationExpressionTranslator.cs deleted file mode 100644 index 1c2e5275e6f..00000000000 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/CompareToMethodToAggregationExpressionTranslator.cs +++ /dev/null @@ -1,43 +0,0 @@ -/* 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.Reflection; - -namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.MethodTranslators -{ - internal static class CompareToMethodToAggregationExpressionTranslator - { - public static TranslatedExpression Translate(TranslationContext context, MethodCallExpression expression) - { - var method = expression.Method; - var arguments = expression.Arguments; - - if (IComparableMethod.IsCompareToMethod(method)) - { - var objectExpression = expression.Object; - var objectTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, objectExpression); - var otherExpression = arguments[0]; - var otherTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, otherExpression); - var ast = AstExpression.Cmp(objectTranslation.Ast, otherTranslation.Ast); - return new TranslatedExpression(expression, ast, new Int32Serializer()); - } - - throw new ExpressionNotSupportedException(expression); - } - } -} diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ExpressionTranslators/CompareComparisonExpressionToFilterTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ExpressionTranslators/CompareComparisonExpressionToFilterTranslator.cs index 0110ec5e238..6758a9cfb6d 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ExpressionTranslators/CompareComparisonExpressionToFilterTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ExpressionTranslators/CompareComparisonExpressionToFilterTranslator.cs @@ -29,7 +29,7 @@ public static bool CanTranslate(Expression leftExpression) return leftExpression is MethodCallExpression leftMethodCallExpression && leftMethodCallExpression.Method is var method && - (IsStaticCompareMethod(method) || IsInstanceCompareToMethod(method)); + (method.IsStaticCompareMethod() || method.IsInstanceCompareToMethod() || method.Is(StringMethod.StaticCompareWithIgnoreCase)); } // caller is responsible for ensuring constant is on the right @@ -60,6 +60,16 @@ public static AstFilter Translate( innerValueExpression = compareArguments[0]; } + if (compareMethod.Is(StringMethod.StaticCompareWithIgnoreCase)) + { + var ignoreCaseExpression = compareArguments[2]; + var ignoreCase = ignoreCaseExpression.GetConstantValue(containingExpression: compareMethodCallExpression); + if (ignoreCase) + { + throw new ExpressionNotSupportedException(compareMethodCallExpression, because: "ignoreCase must be false"); + } + } + var fieldComparisonOperator = (outerComparisonOperator, outerValue) switch { (AstComparisonFilterOperator.Eq, -1) => AstComparisonFilterOperator.Lt, @@ -92,31 +102,5 @@ public static AstFilter Translate( throw new ExpressionNotSupportedException(expression); } - - private static bool IsInstanceCompareToMethod(MethodInfo method) - { - return - method.IsPublic && - !method.IsStatic && - method.ReturnType == typeof(int) && - method.Name == "CompareTo" && - method.GetParameters() is var parameters && - parameters.Length == 1 && - parameters[0].ParameterType is var parameterType && - (parameterType == method.DeclaringType || parameterType == typeof(object)); - } - - private static bool IsStaticCompareMethod(MethodInfo method) - { - return - method.IsPublic && - method.IsStatic && - method.ReturnType == typeof(int) && - method.Name == "Compare" && - method.GetParameters() is var parameters && - parameters.Length == 2 && - parameters[0].ParameterType == method.DeclaringType && - parameters[1].ParameterType == method.DeclaringType; - } } } diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5730Tests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5730Tests.cs index 6461011754c..4a42eadf2d5 100644 --- a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5730Tests.cs +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5730Tests.cs @@ -30,18 +30,18 @@ public CSharp5730Tests(ClassFixture fixture) } [Theory] - [InlineData( 1, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] - [InlineData( 2, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] - [InlineData( 3, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { false, false, false, false, false, false })] - [InlineData( 4, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] - [InlineData( 5, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] - [InlineData( 6, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] - [InlineData( 7, "{ $project : { _v : { $gt : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] - [InlineData( 8, "{ $project : { _v : { $gt : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, false, false, false, false, false })] - [InlineData( 9, "{ $project : { _v : { $lt : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] - [InlineData( 10, "{ $project : { _v : { $lt : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] - [InlineData( 11, "{ $project : { _v : { $lte : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] - [InlineData( 12, "{ $project : { _v : { $gte : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] + [InlineData(1, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] + [InlineData(2, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] + [InlineData(3, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { false, false, false, false, false, false })] + [InlineData(4, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] + [InlineData(5, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] + [InlineData(6, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] + [InlineData(7, "{ $project : { _v : { $gt : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] + [InlineData(8, "{ $project : { _v : { $gt : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, false, false, false, false, false })] + [InlineData(9, "{ $project : { _v : { $lt : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] + [InlineData(10, "{ $project : { _v : { $lt : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] + [InlineData(11, "{ $project : { _v : { $lte : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] + [InlineData(12, "{ $project : { _v : { $gte : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] public void Select_String_Compare_constant_to_field_should_work(int scenario, string expectedStage, bool[] expectedResults) { var collection = Fixture.Collection; @@ -49,15 +49,15 @@ public void Select_String_Compare_constant_to_field_should_work(int scenario, st var queryable = scenario switch { // Compare field to constant - 1 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) == -1), - 2 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) == 0), - 3 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) == 1), - 4 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) != -1), - 5 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) != 0), - 6 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) != 1), - 7 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) > -1), - 8 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) > 0), - 9 => collection.AsQueryable().Select(x => string.Compare ("A", x.B) < 0), + 1 => collection.AsQueryable().Select(x => string.Compare("A", x.B) == -1), + 2 => collection.AsQueryable().Select(x => string.Compare("A", x.B) == 0), + 3 => collection.AsQueryable().Select(x => string.Compare("A", x.B) == 1), + 4 => collection.AsQueryable().Select(x => string.Compare("A", x.B) != -1), + 5 => collection.AsQueryable().Select(x => string.Compare("A", x.B) != 0), + 6 => collection.AsQueryable().Select(x => string.Compare("A", x.B) != 1), + 7 => collection.AsQueryable().Select(x => string.Compare("A", x.B) > -1), + 8 => collection.AsQueryable().Select(x => string.Compare("A", x.B) > 0), + 9 => collection.AsQueryable().Select(x => string.Compare("A", x.B) < 0), 10 => collection.AsQueryable().Select(x => string.Compare("A", x.B) < 1), 11 => collection.AsQueryable().Select(x => string.Compare("A", x.B) <= 0), 12 => collection.AsQueryable().Select(x => string.Compare("A", x.B) >= 0), @@ -68,18 +68,68 @@ public void Select_String_Compare_constant_to_field_should_work(int scenario, st } [Theory] - [InlineData( 1, "{ $project : { _v : { $eq : [{ $cmp : ['$A', 'B'] }, -1] }, _id : 0 } }", new bool[] { true, true, false, false, false, false })] - [InlineData( 2, "{ $project : { _v : { $eq : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, false, false, false })] - [InlineData( 3, "{ $project : { _v : { $eq : [{ $cmp : ['$A', 'B'] }, 1] }, _id : 0 } }", new bool[] { false, false, false, true, true, true })] - [InlineData( 4, "{ $project : { _v : { $ne : [{ $cmp : ['$A', 'B'] }, -1] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] - [InlineData( 5, "{ $project : { _v : { $ne : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, true, true, true })] - [InlineData( 6, "{ $project : { _v : { $ne : [{ $cmp : ['$A', 'B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] - [InlineData( 7, "{ $project : { _v : { $gt : [{ $cmp : ['$A', 'B'] }, -1] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] - [InlineData( 8, "{ $project : { _v : { $gt : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, false, true, true, true })] - [InlineData( 9, "{ $project : { _v : { $lt : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, false, false, false })] - [InlineData( 10, "{ $project : { _v : { $lt : [{ $cmp : ['$A', 'B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] - [InlineData( 11, "{ $project : { _v : { $lte : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] - [InlineData( 12, "{ $project : { _v : { $gte : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] + [InlineData(1, false, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] + [InlineData(2, false, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] + [InlineData(3, false, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { false, false, false, false, false, false })] + [InlineData(4, false, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] + [InlineData(5, false, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] + [InlineData(6, false, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] + [InlineData(7, false, "{ $project : { _v : { $gt : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] + [InlineData(8, false, "{ $project : { _v : { $gt : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, false, false, false, false, false })] + [InlineData(9, false, "{ $project : { _v : { $lt : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] + [InlineData(10, false, "{ $project : { _v : { $lt : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] + [InlineData(11, false, "{ $project : { _v : { $lte : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] + [InlineData(12, false, "{ $project : { _v : { $gte : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] + [InlineData(1, true, "{ $project : { _v : { $eq : [{ $strcasecmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { false, true, false, false, true, false })] + [InlineData(2, true, "{ $project : { _v : { $eq : [{ $strcasecmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] + [InlineData(3, true, "{ $project : { _v : { $eq : [{ $strcasecmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { false, false, false, false, false, false })] + [InlineData(4, true, "{ $project : { _v : { $ne : [{ $strcasecmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] + [InlineData(5, true, "{ $project : { _v : { $ne : [{ $strcasecmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, false, true, false })] + [InlineData(6, true, "{ $project : { _v : { $ne : [{ $strcasecmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] + [InlineData(7, true, "{ $project : { _v : { $gt : [{ $strcasecmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] + [InlineData(8, true, "{ $project : { _v : { $gt : [{ $strcasecmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, false, false, false, false, false })] + [InlineData(9, true, "{ $project : { _v : { $lt : [{ $strcasecmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, false, true, false })] + [InlineData(10, true, "{ $project : { _v : { $lt : [{ $strcasecmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] + [InlineData(11, true, "{ $project : { _v : { $lte : [{ $strcasecmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] + [InlineData(12, true, "{ $project : { _v : { $gte : [{ $strcasecmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] + public void Select_String_Compare_constant_to_field_with_ignoreCase_should_work(int scenario, bool ignoreCase, string expectedStage, bool[] expectedResults) + { + var collection = Fixture.Collection; + + var queryable = scenario switch + { + // Compare field to constant + 1 => collection.AsQueryable().Select(x => string.Compare("A", x.B, ignoreCase) == -1), + 2 => collection.AsQueryable().Select(x => string.Compare("A", x.B, ignoreCase) == 0), + 3 => collection.AsQueryable().Select(x => string.Compare("A", x.B, ignoreCase) == 1), + 4 => collection.AsQueryable().Select(x => string.Compare("A", x.B, ignoreCase) != -1), + 5 => collection.AsQueryable().Select(x => string.Compare("A", x.B, ignoreCase) != 0), + 6 => collection.AsQueryable().Select(x => string.Compare("A", x.B, ignoreCase) != 1), + 7 => collection.AsQueryable().Select(x => string.Compare("A", x.B, ignoreCase) > -1), + 8 => collection.AsQueryable().Select(x => string.Compare("A", x.B, ignoreCase) > 0), + 9 => collection.AsQueryable().Select(x => string.Compare("A", x.B, ignoreCase) < 0), + 10 => collection.AsQueryable().Select(x => string.Compare("A", x.B, ignoreCase) < 1), + 11 => collection.AsQueryable().Select(x => string.Compare("A", x.B, ignoreCase) <= 0), + 12 => collection.AsQueryable().Select(x => string.Compare("A", x.B, ignoreCase) >= 0), + _ => throw new ArgumentException($"Invalid scenario: {scenario}.") + }; + + Assert(collection, queryable, expectedStage, expectedResults); + } + + [Theory] + [InlineData(1, "{ $project : { _v : { $eq : [{ $cmp : ['$A', 'B'] }, -1] }, _id : 0 } }", new bool[] { true, true, false, false, false, false })] + [InlineData(2, "{ $project : { _v : { $eq : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, false, false, false })] + [InlineData(3, "{ $project : { _v : { $eq : [{ $cmp : ['$A', 'B'] }, 1] }, _id : 0 } }", new bool[] { false, false, false, true, true, true })] + [InlineData(4, "{ $project : { _v : { $ne : [{ $cmp : ['$A', 'B'] }, -1] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] + [InlineData(5, "{ $project : { _v : { $ne : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, true, true, true })] + [InlineData(6, "{ $project : { _v : { $ne : [{ $cmp : ['$A', 'B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] + [InlineData(7, "{ $project : { _v : { $gt : [{ $cmp : ['$A', 'B'] }, -1] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] + [InlineData(8, "{ $project : { _v : { $gt : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, false, true, true, true })] + [InlineData(9, "{ $project : { _v : { $lt : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, false, false, false })] + [InlineData(10, "{ $project : { _v : { $lt : [{ $cmp : ['$A', 'B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] + [InlineData(11, "{ $project : { _v : { $lte : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] + [InlineData(12, "{ $project : { _v : { $gte : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] public void Select_String_Compare_field_to_constant_should_work(int scenario, string expectedStage, bool[] expectedResults) { var collection = Fixture.Collection; @@ -106,18 +156,68 @@ public void Select_String_Compare_field_to_constant_should_work(int scenario, st } [Theory] - [InlineData( 1, "{ $project : { _v : { $eq : [{ $cmp : ['$A', '$B'] }, -1] }, _id : 0 } }", new bool[] { false, true, false, false, true, false })] - [InlineData( 2, "{ $project : { _v : { $eq : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, false, true, false, false })] - [InlineData( 3, "{ $project : { _v : { $eq : [{ $cmp : ['$A', '$B'] }, 1] }, _id : 0 } }", new bool[] { false, false, true, false, false, true })] - [InlineData( 4, "{ $project : { _v : { $ne : [{ $cmp : ['$A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] - [InlineData( 5, "{ $project : { _v : { $ne : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, true, false, true, true })] - [InlineData( 6, "{ $project : { _v : { $ne : [{ $cmp : ['$A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] - [InlineData( 7, "{ $project : { _v : { $gt : [{ $cmp : ['$A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] - [InlineData( 8, "{ $project : { _v : { $gt : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, false, false, true })] - [InlineData( 9, "{ $project : { _v : { $lt : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, false, true, false })] - [InlineData( 10, "{ $project : { _v : { $lt : [{ $cmp : ['$A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] - [InlineData( 11, "{ $project : { _v : { $lte : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] - [InlineData( 12, "{ $project : { _v : { $gte : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] + [InlineData(1, false, "{ $project : { _v : { $eq : [{ $cmp : ['$A', 'B'] }, -1] }, _id : 0 } }", new bool[] { true, true, false, false, false, false })] + [InlineData(2, false, "{ $project : { _v : { $eq : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, false, false, false })] + [InlineData(3, false, "{ $project : { _v : { $eq : [{ $cmp : ['$A', 'B'] }, 1] }, _id : 0 } }", new bool[] { false, false, false, true, true, true })] + [InlineData(4, false, "{ $project : { _v : { $ne : [{ $cmp : ['$A', 'B'] }, -1] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] + [InlineData(5, false, "{ $project : { _v : { $ne : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, true, true, true })] + [InlineData(6, false, "{ $project : { _v : { $ne : [{ $cmp : ['$A', 'B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] + [InlineData(7, false, "{ $project : { _v : { $gt : [{ $cmp : ['$A', 'B'] }, -1] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] + [InlineData(8, false, "{ $project : { _v : { $gt : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, false, true, true, true })] + [InlineData(9, false, "{ $project : { _v : { $lt : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, false, false, false })] + [InlineData(10, false, "{ $project : { _v : { $lt : [{ $cmp : ['$A', 'B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] + [InlineData(11, false, "{ $project : { _v : { $lte : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] + [InlineData(12, false, "{ $project : { _v : { $gte : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] + [InlineData(1, true, "{ $project : { _v : { $eq : [{ $strcasecmp : ['$A', 'B'] }, -1] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] + [InlineData(2, true, "{ $project : { _v : { $eq : [{ $strcasecmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, false, false, true })] + [InlineData(3, true, "{ $project : { _v : { $eq : [{ $strcasecmp : ['$A', 'B'] }, 1] }, _id : 0 } }", new bool[] { false, false, false, false, false, false })] + [InlineData(4, true, "{ $project : { _v : { $ne : [{ $strcasecmp : ['$A', 'B'] }, -1] }, _id : 0 } }", new bool[] { false, false, true, false, false, true })] + [InlineData(5, true, "{ $project : { _v : { $ne : [{ $strcasecmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] + [InlineData(6, true, "{ $project : { _v : { $ne : [{ $strcasecmp : ['$A', 'B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] + [InlineData(7, true, "{ $project : { _v : { $gt : [{ $strcasecmp : ['$A', 'B'] }, -1] }, _id : 0 } }", new bool[] { false, false, true, false, false, true })] + [InlineData(8, true, "{ $project : { _v : { $gt : [{ $strcasecmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, false, false, false, false })] + [InlineData(9, true, "{ $project : { _v : { $lt : [{ $strcasecmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] + [InlineData(10, true, "{ $project : { _v : { $lt : [{ $strcasecmp : ['$A', 'B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] + [InlineData(11, true, "{ $project : { _v : { $lte : [{ $strcasecmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] + [InlineData(12, true, "{ $project : { _v : { $gte : [{ $strcasecmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, false, false, true })] + public void Select_String_Compare_field_to_constant_with_ignoreCase_should_work(int scenario, bool ignoreCase, string expectedStage, bool[] expectedResults) + { + var collection = Fixture.Collection; + + var queryable = scenario switch + { + // Compare field to constant + 1 => collection.AsQueryable().Select(x => string.Compare(x.A, "B", ignoreCase) == -1), + 2 => collection.AsQueryable().Select(x => string.Compare(x.A, "B", ignoreCase) == 0), + 3 => collection.AsQueryable().Select(x => string.Compare(x.A, "B", ignoreCase) == 1), + 4 => collection.AsQueryable().Select(x => string.Compare(x.A, "B", ignoreCase) != -1), + 5 => collection.AsQueryable().Select(x => string.Compare(x.A, "B", ignoreCase) != 0), + 6 => collection.AsQueryable().Select(x => string.Compare(x.A, "B", ignoreCase) != 1), + 7 => collection.AsQueryable().Select(x => string.Compare(x.A, "B", ignoreCase) > -1), + 8 => collection.AsQueryable().Select(x => string.Compare(x.A, "B", ignoreCase) > 0), + 9 => collection.AsQueryable().Select(x => string.Compare(x.A, "B", ignoreCase) < 0), + 10 => collection.AsQueryable().Select(x => string.Compare(x.A, "B", ignoreCase) < 1), + 11 => collection.AsQueryable().Select(x => string.Compare(x.A, "B", ignoreCase) <= 0), + 12 => collection.AsQueryable().Select(x => string.Compare(x.A, "B", ignoreCase) >= 0), + _ => throw new ArgumentException($"Invalid scenario: {scenario}.") + }; + + Assert(collection, queryable, expectedStage, expectedResults); + } + + [Theory] + [InlineData(1, "{ $project : { _v : { $eq : [{ $cmp : ['$A', '$B'] }, -1] }, _id : 0 } }", new bool[] { false, true, false, false, true, false })] + [InlineData(2, "{ $project : { _v : { $eq : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, false, true, false, false })] + [InlineData(3, "{ $project : { _v : { $eq : [{ $cmp : ['$A', '$B'] }, 1] }, _id : 0 } }", new bool[] { false, false, true, false, false, true })] + [InlineData(4, "{ $project : { _v : { $ne : [{ $cmp : ['$A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] + [InlineData(5, "{ $project : { _v : { $ne : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, true, false, true, true })] + [InlineData(6, "{ $project : { _v : { $ne : [{ $cmp : ['$A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] + [InlineData(7, "{ $project : { _v : { $gt : [{ $cmp : ['$A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] + [InlineData(8, "{ $project : { _v : { $gt : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, false, false, true })] + [InlineData(9, "{ $project : { _v : { $lt : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, false, true, false })] + [InlineData(10, "{ $project : { _v : { $lt : [{ $cmp : ['$A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] + [InlineData(11, "{ $project : { _v : { $lte : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] + [InlineData(12, "{ $project : { _v : { $gte : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] public void Select_String_Compare_field_to_field_should_work(int scenario, string expectedStage, bool[] expectedResults) { var collection = Fixture.Collection; @@ -144,15 +244,65 @@ public void Select_String_Compare_field_to_field_should_work(int scenario, strin } [Theory] - [InlineData( 1, "{ $match : { B : { $gt : 'A' } } }", new int[] { 2, 4, 5, 6 })] - [InlineData( 2, "{ $match : { B : 'A' } }", new int[] { 1, 3 })] - [InlineData( 3, "{ $match : { B : { $lt : 'A' } } }", new int[] { })] - [InlineData( 4, "{ $match : { B : { $lte : 'A' } } }", new int[] { 1, 3 })] - [InlineData( 5, "{ $match : { B : { $ne : 'A' } } }", new int[] { 2, 4, 5, 6 })] - [InlineData( 6, "{ $match : { B : { $gte : 'A' } } }", new int[] { 1, 2, 3, 4, 5, 6 })] - [InlineData( 7, "{ $match : { B : { $lte : 'A' } } }", new int[] { 1, 3 })] - [InlineData( 8, "{ $match : { B : { $lt : 'A' } } }", new int[] { })] - [InlineData( 9, "{ $match : { B : { $gt : 'A' } } }", new int[] { 2, 4, 5, 6 })] + [InlineData(1, false, "{ $project : { _v : { $eq : [{ $cmp : ['$A', '$B'] }, -1] }, _id : 0 } }", new bool[] { false, true, false, false, true, false })] + [InlineData(2, false, "{ $project : { _v : { $eq : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, false, true, false, false })] + [InlineData(3, false, "{ $project : { _v : { $eq : [{ $cmp : ['$A', '$B'] }, 1] }, _id : 0 } }", new bool[] { false, false, true, false, false, true })] + [InlineData(4, false, "{ $project : { _v : { $ne : [{ $cmp : ['$A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] + [InlineData(5, false, "{ $project : { _v : { $ne : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, true, false, true, true })] + [InlineData(6, false, "{ $project : { _v : { $ne : [{ $cmp : ['$A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] + [InlineData(7, false, "{ $project : { _v : { $gt : [{ $cmp : ['$A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] + [InlineData(8, false, "{ $project : { _v : { $gt : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, false, false, true })] + [InlineData(9, false, "{ $project : { _v : { $lt : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, false, true, false })] + [InlineData(10, false, "{ $project : { _v : { $lt : [{ $cmp : ['$A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] + [InlineData(11, false, "{ $project : { _v : { $lte : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] + [InlineData(12, false, "{ $project : { _v : { $gte : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] + [InlineData(1, true, "{ $project : { _v : { $eq : [{ $strcasecmp : ['$A', '$B'] }, -1] }, _id : 0 } }", new bool[] { false, true, false, false, true, false })] + [InlineData(2, true, "{ $project : { _v : { $eq : [{ $strcasecmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, false, true, false, false })] + [InlineData(3, true, "{ $project : { _v : { $eq : [{ $strcasecmp : ['$A', '$B'] }, 1] }, _id : 0 } }", new bool[] { false, false, true, false, false, true })] + [InlineData(4, true, "{ $project : { _v : { $ne : [{ $strcasecmp : ['$A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] + [InlineData(5, true, "{ $project : { _v : { $ne : [{ $strcasecmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, true, false, true, true })] + [InlineData(6, true, "{ $project : { _v : { $ne : [{ $strcasecmp : ['$A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] + [InlineData(7, true, "{ $project : { _v : { $gt : [{ $strcasecmp : ['$A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] + [InlineData(8, true, "{ $project : { _v : { $gt : [{ $strcasecmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, false, false, true })] + [InlineData(9, true, "{ $project : { _v : { $lt : [{ $strcasecmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, false, true, false })] + [InlineData(10, true, "{ $project : { _v : { $lt : [{ $strcasecmp : ['$A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] + [InlineData(11, true, "{ $project : { _v : { $lte : [{ $strcasecmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] + [InlineData(12, true, "{ $project : { _v : { $gte : [{ $strcasecmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] + public void Select_String_Compare_field_to_field_with_ignoreCase_should_work(int scenario, bool ignoreCase, string expectedStage, bool[] expectedResults) + { + var collection = Fixture.Collection; + + var queryable = scenario switch + { + // Compare field to constant + 1 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B, ignoreCase) == -1), + 2 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B, ignoreCase) == 0), + 3 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B, ignoreCase) == 1), + 4 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B, ignoreCase) != -1), + 5 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B, ignoreCase) != 0), + 6 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B, ignoreCase) != 1), + 7 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B, ignoreCase) > -1), + 8 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B, ignoreCase) > 0), + 9 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B, ignoreCase) < 0), + 10 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B, ignoreCase) < 1), + 11 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B, ignoreCase) <= 0), + 12 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B, ignoreCase) >= 0), + _ => throw new ArgumentException($"Invalid scenario: {scenario}.") + }; + + Assert(collection, queryable, expectedStage, expectedResults); + } + + [Theory] + [InlineData(1, "{ $match : { B : { $gt : 'A' } } }", new int[] { 2, 4, 5, 6 })] + [InlineData(2, "{ $match : { B : 'A' } }", new int[] { 1, 3 })] + [InlineData(3, "{ $match : { B : { $lt : 'A' } } }", new int[] { })] + [InlineData(4, "{ $match : { B : { $lte : 'A' } } }", new int[] { 1, 3 })] + [InlineData(5, "{ $match : { B : { $ne : 'A' } } }", new int[] { 2, 4, 5, 6 })] + [InlineData(6, "{ $match : { B : { $gte : 'A' } } }", new int[] { 1, 2, 3, 4, 5, 6 })] + [InlineData(7, "{ $match : { B : { $lte : 'A' } } }", new int[] { 1, 3 })] + [InlineData(8, "{ $match : { B : { $lt : 'A' } } }", new int[] { })] + [InlineData(9, "{ $match : { B : { $gt : 'A' } } }", new int[] { 2, 4, 5, 6 })] [InlineData(10, "{ $match : { B : { $gte : 'A' } } }", new int[] { 1, 2, 3, 4, 5, 6 })] [InlineData(11, "{ $match : { B : { $gte : 'A' } } }", new int[] { 1, 2, 3, 4, 5, 6 })] [InlineData(12, "{ $match : { B : { $lte : 'A' } } }", new int[] { 1, 3 })] @@ -163,15 +313,15 @@ public void Where_String_Compare_constant_to_field_should_work(int scenario, str var queryable = scenario switch { // Compare field to constant - 1 => collection.AsQueryable().Where(x => string.Compare ("A", x.B) == -1), - 2 => collection.AsQueryable().Where(x => string.Compare ("A", x.B) == 0), - 3 => collection.AsQueryable().Where(x => string.Compare ("A", x.B) == 1), - 4 => collection.AsQueryable().Where(x => string.Compare ("A", x.B) != -1), - 5 => collection.AsQueryable().Where(x => string.Compare ("A", x.B) != 0), - 6 => collection.AsQueryable().Where(x => string.Compare ("A", x.B) != 1), - 7 => collection.AsQueryable().Where(x => string.Compare ("A", x.B) > -1), - 8 => collection.AsQueryable().Where(x => string.Compare ("A", x.B) > 0), - 9 => collection.AsQueryable().Where(x => string.Compare ("A", x.B) < 0), + 1 => collection.AsQueryable().Where(x => string.Compare("A", x.B) == -1), + 2 => collection.AsQueryable().Where(x => string.Compare("A", x.B) == 0), + 3 => collection.AsQueryable().Where(x => string.Compare("A", x.B) == 1), + 4 => collection.AsQueryable().Where(x => string.Compare("A", x.B) != -1), + 5 => collection.AsQueryable().Where(x => string.Compare("A", x.B) != 0), + 6 => collection.AsQueryable().Where(x => string.Compare("A", x.B) != 1), + 7 => collection.AsQueryable().Where(x => string.Compare("A", x.B) > -1), + 8 => collection.AsQueryable().Where(x => string.Compare("A", x.B) > 0), + 9 => collection.AsQueryable().Where(x => string.Compare("A", x.B) < 0), 10 => collection.AsQueryable().Where(x => string.Compare("A", x.B) < 1), 11 => collection.AsQueryable().Where(x => string.Compare("A", x.B) <= 0), 12 => collection.AsQueryable().Where(x => string.Compare("A", x.B) >= 0), @@ -182,15 +332,65 @@ public void Where_String_Compare_constant_to_field_should_work(int scenario, str } [Theory] - [InlineData( 1, "{ $match : { A : { $lt : 'B' } } }", new int[] { 1, 2 })] - [InlineData( 2, "{ $match : { A : 'B' } }", new int[] { 3 })] - [InlineData( 3, "{ $match : { A : { $gt : 'B' } } }", new int[] { 4, 5, 6 })] - [InlineData( 4, "{ $match : { A : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] - [InlineData( 5, "{ $match : { A : { $ne : 'B' } } }", new int[] { 1, 2, 4, 5, 6 })] - [InlineData( 6, "{ $match : { A : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] - [InlineData( 7, "{ $match : { A : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] - [InlineData( 8, "{ $match : { A : { $gt : 'B' } } }", new int[] { 4, 5, 6 })] - [InlineData( 9, "{ $match : { A : { $lt : 'B' } } }", new int[] { 1, 2 })] + [InlineData(1, false, "{ $match : { B : { $gt : 'A' } } }", new int[] { 2, 4, 5, 6 })] + [InlineData(2, false, "{ $match : { B : 'A' } }", new int[] { 1, 3 })] + [InlineData(3, false, "{ $match : { B : { $lt : 'A' } } }", new int[] { })] + [InlineData(4, false, "{ $match : { B : { $lte : 'A' } } }", new int[] { 1, 3 })] + [InlineData(5, false, "{ $match : { B : { $ne : 'A' } } }", new int[] { 2, 4, 5, 6 })] + [InlineData(6, false, "{ $match : { B : { $gte : 'A' } } }", new int[] { 1, 2, 3, 4, 5, 6 })] + [InlineData(7, false, "{ $match : { B : { $lte : 'A' } } }", new int[] { 1, 3 })] + [InlineData(8, false, "{ $match : { B : { $lt : 'A' } } }", new int[] { })] + [InlineData(9, false, "{ $match : { B : { $gt : 'A' } } }", new int[] { 2, 4, 5, 6 })] + [InlineData(10, false, "{ $match : { B : { $gte : 'A' } } }", new int[] { 1, 2, 3, 4, 5, 6 })] + [InlineData(11, false, "{ $match : { B : { $gte : 'A' } } }", new int[] { 1, 2, 3, 4, 5, 6 })] + [InlineData(12, false, "{ $match : { B : { $lte : 'A' } } }", new int[] { 1, 3 })] + [InlineData(1, true, "{ $match : { $expr : { $eq : [{ $strcasecmp : ['A', '$B'] }, -1] } } }", new int[] { 2, 5 })] + [InlineData(2, true, "{ $match : { $expr : { $eq : [{ $strcasecmp : ['A', '$B'] }, 0] } } }", new int[] { 1, 3, 4, 6 })] + [InlineData(3, true, "{ $match : { $expr : { $eq : [{ $strcasecmp : ['A', '$B'] }, 1] } } }", new int[] { })] + [InlineData(4, true, "{ $match : { $expr : { $ne : [{ $strcasecmp : ['A', '$B'] }, -1] } } }", new int[] { 1, 3, 4, 6 })] + [InlineData(5, true, "{ $match : { $expr : { $ne : [{ $strcasecmp : ['A', '$B'] }, 0] } } }", new int[] { 2, 5 })] + [InlineData(6, true, "{ $match : { $expr : { $ne : [{ $strcasecmp : ['A', '$B'] }, 1] } } }", new int[] { 1, 2, 3, 4, 5, 6 })] + [InlineData(7, true, "{ $match : { $expr : { $gt : [{ $strcasecmp : ['A', '$B'] }, -1] } } }", new int[] { 1, 3, 4, 6 })] + [InlineData(8, true, "{ $match : { $expr : { $gt : [{ $strcasecmp : ['A', '$B'] }, 0] } } }", new int[] { })] + [InlineData(9, true, "{ $match : { $expr : { $lt : [{ $strcasecmp : ['A', '$B'] }, 0] } } }", new int[] { 2, 5 })] + [InlineData(10, true, "{ $match : { $expr : { $lt : [{ $strcasecmp : ['A', '$B'] }, 1] } } }", new int[] { 1, 2, 3, 4, 5, 6 })] + [InlineData(11, true, "{ $match : { $expr : { $lte : [{ $strcasecmp : ['A', '$B'] }, 0] } } }", new int[] { 1, 2, 3, 4, 5, 6 })] + [InlineData(12, true, "{ $match : { $expr : { $gte : [{ $strcasecmp : ['A', '$B'] }, 0] } } }", new int[] { 1, 3, 4, 6 })] + public void Where_String_Compare_constant_to_field_with_ignoreCase_should_work(int scenario, bool ignoreCase, string expectedStage, int[] expectedIds) + { + var collection = Fixture.Collection; + + var queryable = scenario switch + { + // Compare field to constant + 1 => collection.AsQueryable().Where(x => string.Compare("A", x.B, ignoreCase) == -1), + 2 => collection.AsQueryable().Where(x => string.Compare("A", x.B, ignoreCase) == 0), + 3 => collection.AsQueryable().Where(x => string.Compare("A", x.B, ignoreCase) == 1), + 4 => collection.AsQueryable().Where(x => string.Compare("A", x.B, ignoreCase) != -1), + 5 => collection.AsQueryable().Where(x => string.Compare("A", x.B, ignoreCase) != 0), + 6 => collection.AsQueryable().Where(x => string.Compare("A", x.B, ignoreCase) != 1), + 7 => collection.AsQueryable().Where(x => string.Compare("A", x.B, ignoreCase) > -1), + 8 => collection.AsQueryable().Where(x => string.Compare("A", x.B, ignoreCase) > 0), + 9 => collection.AsQueryable().Where(x => string.Compare("A", x.B, ignoreCase) < 0), + 10 => collection.AsQueryable().Where(x => string.Compare("A", x.B, ignoreCase) < 1), + 11 => collection.AsQueryable().Where(x => string.Compare("A", x.B, ignoreCase) <= 0), + 12 => collection.AsQueryable().Where(x => string.Compare("A", x.B, ignoreCase) >= 0), + _ => throw new ArgumentException($"Invalid scenario: {scenario}.") + }; + + AssertIds(collection, queryable, expectedStage, expectedIds); + } + + [Theory] + [InlineData(1, "{ $match : { A : { $lt : 'B' } } }", new int[] { 1, 2 })] + [InlineData(2, "{ $match : { A : 'B' } }", new int[] { 3 })] + [InlineData(3, "{ $match : { A : { $gt : 'B' } } }", new int[] { 4, 5, 6 })] + [InlineData(4, "{ $match : { A : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] + [InlineData(5, "{ $match : { A : { $ne : 'B' } } }", new int[] { 1, 2, 4, 5, 6 })] + [InlineData(6, "{ $match : { A : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] + [InlineData(7, "{ $match : { A : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] + [InlineData(8, "{ $match : { A : { $gt : 'B' } } }", new int[] { 4, 5, 6 })] + [InlineData(9, "{ $match : { A : { $lt : 'B' } } }", new int[] { 1, 2 })] [InlineData(10, "{ $match : { A : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] [InlineData(11, "{ $match : { A : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] [InlineData(12, "{ $match : { A : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] @@ -220,18 +420,68 @@ public void Where_String_Compare_field_to_constant_should_work(int scenario, str } [Theory] - [InlineData( 1, "{ $match : { $expr : { $eq : [{ $cmp : ['$A', '$B'] }, -1] } } }", new int[] { 2, 5 })] - [InlineData( 2, "{ $match : { $expr : { $eq : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 1, 4 })] - [InlineData( 3, "{ $match : { $expr : { $eq : [{ $cmp : ['$A', '$B'] }, 1] } } }", new int[] { 3, 6 })] - [InlineData( 4, "{ $match : { $expr : { $ne : [{ $cmp : ['$A', '$B'] }, -1] } } }", new int[] { 1, 3, 4, 6 })] - [InlineData( 5, "{ $match : { $expr : { $ne : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 2, 3, 5, 6 })] - [InlineData( 6, "{ $match : { $expr : { $ne : [{ $cmp : ['$A', '$B'] }, 1] } } }", new int[] { 1, 2, 4, 5 })] - [InlineData( 7, "{ $match : { $expr : { $gt : [{ $cmp : ['$A', '$B'] }, -1] } } }", new int[] { 1, 3, 4, 6 })] - [InlineData( 8, "{ $match : { $expr : { $gt : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 3, 6 })] - [InlineData( 9, "{ $match : { $expr : { $lt : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 2, 5 })] - [InlineData( 10, "{ $match : { $expr : { $lt : [{ $cmp : ['$A', '$B'] }, 1] } } }", new int[] { 1, 2, 4, 5 })] - [InlineData( 11, "{ $match : { $expr : { $lte : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 1, 2, 4, 5 })] - [InlineData( 12, "{ $match : { $expr : { $gte : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 1, 3, 4, 6 })] + [InlineData(1, false, "{ $match : { A : { $lt : 'B' } } }", new int[] { 1, 2 })] + [InlineData(2, false, "{ $match : { A : 'B' } }", new int[] { 3 })] + [InlineData(3, false, "{ $match : { A : { $gt : 'B' } } }", new int[] { 4, 5, 6 })] + [InlineData(4, false, "{ $match : { A : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] + [InlineData(5, false, "{ $match : { A : { $ne : 'B' } } }", new int[] { 1, 2, 4, 5, 6 })] + [InlineData(6, false, "{ $match : { A : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] + [InlineData(7, false, "{ $match : { A : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] + [InlineData(8, false, "{ $match : { A : { $gt : 'B' } } }", new int[] { 4, 5, 6 })] + [InlineData(9, false, "{ $match : { A : { $lt : 'B' } } }", new int[] { 1, 2 })] + [InlineData(10, false, "{ $match : { A : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] + [InlineData(11, false, "{ $match : { A : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] + [InlineData(12, false, "{ $match : { A : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] + [InlineData(1, true, "{ $match : { $expr : { $eq : [{ $strcasecmp : ['$A', 'B'] }, -1] } } }", new int[] { 1, 2, 4, 5 })] + [InlineData(2, true, "{ $match : { $expr : { $eq : [{ $strcasecmp : ['$A', 'B'] }, 0] } } }", new int[] { 3, 6 })] + [InlineData(3, true, "{ $match : { $expr : { $eq : [{ $strcasecmp : ['$A', 'B'] }, 1] } } }", new int[] { })] + [InlineData(4, true, "{ $match : { $expr : { $ne : [{ $strcasecmp : ['$A', 'B'] }, -1] } } }", new int[] { 3, 6 })] + [InlineData(5, true, "{ $match : { $expr : { $ne : [{ $strcasecmp : ['$A', 'B'] }, 0] } } }", new int[] { 1, 2, 4, 5 })] + [InlineData(6, true, "{ $match : { $expr : { $ne : [{ $strcasecmp : ['$A', 'B'] }, 1] } } }", new int[] { 1, 2, 3, 4, 5, 6 })] + [InlineData(7, true, "{ $match : { $expr : { $gt : [{ $strcasecmp : ['$A', 'B'] }, -1] } } }", new int[] { 3, 6 })] + [InlineData(8, true, "{ $match : { $expr : { $gt : [{ $strcasecmp : ['$A', 'B'] }, 0] } } }", new int[] { })] + [InlineData(9, true, "{ $match : { $expr : { $lt : [{ $strcasecmp : ['$A', 'B'] }, 0] } } }", new int[] { 1, 2, 4, 5 })] + [InlineData(10, true, "{ $match : { $expr : { $lt : [{ $strcasecmp : ['$A', 'B'] }, 1] } } }", new int[] { 1, 2, 3, 4, 5, 6 })] + [InlineData(11, true, "{ $match : { $expr : { $lte : [{ $strcasecmp : ['$A', 'B'] }, 0] } } }", new int[] { 1, 2, 3, 4, 5, 6 })] + [InlineData(12, true, "{ $match : { $expr : { $gte : [{ $strcasecmp : ['$A', 'B'] }, 0] } } }", new int[] { 3, 6 })] + public void Where_String_Compare_field_to_constant_with_ignoreCase_should_work(int scenario, bool ignoreCase, string expectedStage, int[] expectedIds) + { + var collection = Fixture.Collection; + + var queryable = scenario switch + { + // Compare field to constant + 1 => collection.AsQueryable().Where(x => string.Compare(x.A, "B", ignoreCase) == -1), + 2 => collection.AsQueryable().Where(x => string.Compare(x.A, "B", ignoreCase) == 0), + 3 => collection.AsQueryable().Where(x => string.Compare(x.A, "B", ignoreCase) == 1), + 4 => collection.AsQueryable().Where(x => string.Compare(x.A, "B", ignoreCase) != -1), + 5 => collection.AsQueryable().Where(x => string.Compare(x.A, "B", ignoreCase) != 0), + 6 => collection.AsQueryable().Where(x => string.Compare(x.A, "B", ignoreCase) != 1), + 7 => collection.AsQueryable().Where(x => string.Compare(x.A, "B", ignoreCase) > -1), + 8 => collection.AsQueryable().Where(x => string.Compare(x.A, "B", ignoreCase) > 0), + 9 => collection.AsQueryable().Where(x => string.Compare(x.A, "B", ignoreCase) < 0), + 10 => collection.AsQueryable().Where(x => string.Compare(x.A, "B", ignoreCase) < 1), + 11 => collection.AsQueryable().Where(x => string.Compare(x.A, "B", ignoreCase) <= 0), + 12 => collection.AsQueryable().Where(x => string.Compare(x.A, "B", ignoreCase) >= 0), + _ => throw new ArgumentException($"Invalid scenario: {scenario}.") + }; + + AssertIds(collection, queryable, expectedStage, expectedIds); + } + + [Theory] + [InlineData(1, "{ $match : { $expr : { $eq : [{ $cmp : ['$A', '$B'] }, -1] } } }", new int[] { 2, 5 })] + [InlineData(2, "{ $match : { $expr : { $eq : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 1, 4 })] + [InlineData(3, "{ $match : { $expr : { $eq : [{ $cmp : ['$A', '$B'] }, 1] } } }", new int[] { 3, 6 })] + [InlineData(4, "{ $match : { $expr : { $ne : [{ $cmp : ['$A', '$B'] }, -1] } } }", new int[] { 1, 3, 4, 6 })] + [InlineData(5, "{ $match : { $expr : { $ne : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 2, 3, 5, 6 })] + [InlineData(6, "{ $match : { $expr : { $ne : [{ $cmp : ['$A', '$B'] }, 1] } } }", new int[] { 1, 2, 4, 5 })] + [InlineData(7, "{ $match : { $expr : { $gt : [{ $cmp : ['$A', '$B'] }, -1] } } }", new int[] { 1, 3, 4, 6 })] + [InlineData(8, "{ $match : { $expr : { $gt : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 3, 6 })] + [InlineData(9, "{ $match : { $expr : { $lt : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 2, 5 })] + [InlineData(10, "{ $match : { $expr : { $lt : [{ $cmp : ['$A', '$B'] }, 1] } } }", new int[] { 1, 2, 4, 5 })] + [InlineData(11, "{ $match : { $expr : { $lte : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 1, 2, 4, 5 })] + [InlineData(12, "{ $match : { $expr : { $gte : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 1, 3, 4, 6 })] public void Where_String_Compare_field_to_field_should_work(int scenario, string expectedStage, int[] expectedIds) { var collection = Fixture.Collection; @@ -257,6 +507,56 @@ public void Where_String_Compare_field_to_field_should_work(int scenario, string AssertIds(collection, queryable, expectedStage, expectedIds); } + [Theory] + [InlineData(1, false, "{ $match : { $expr : { $eq : [{ $cmp : ['$A', '$B'] }, -1] } } }", new int[] { 2, 5 })] + [InlineData(2, false, "{ $match : { $expr : { $eq : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 1, 4 })] + [InlineData(3, false, "{ $match : { $expr : { $eq : [{ $cmp : ['$A', '$B'] }, 1] } } }", new int[] { 3, 6 })] + [InlineData(4, false, "{ $match : { $expr : { $ne : [{ $cmp : ['$A', '$B'] }, -1] } } }", new int[] { 1, 3, 4, 6 })] + [InlineData(5, false, "{ $match : { $expr : { $ne : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 2, 3, 5, 6 })] + [InlineData(6, false, "{ $match : { $expr : { $ne : [{ $cmp : ['$A', '$B'] }, 1] } } }", new int[] { 1, 2, 4, 5 })] + [InlineData(7, false, "{ $match : { $expr : { $gt : [{ $cmp : ['$A', '$B'] }, -1] } } }", new int[] { 1, 3, 4, 6 })] + [InlineData(8, false, "{ $match : { $expr : { $gt : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 3, 6 })] + [InlineData(9, false, "{ $match : { $expr : { $lt : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 2, 5 })] + [InlineData(10, false, "{ $match : { $expr : { $lt : [{ $cmp : ['$A', '$B'] }, 1] } } }", new int[] { 1, 2, 4, 5 })] + [InlineData(11, false, "{ $match : { $expr : { $lte : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 1, 2, 4, 5 })] + [InlineData(12, false, "{ $match : { $expr : { $gte : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 1, 3, 4, 6 })] + [InlineData(1, true, "{ $match : { $expr : { $eq : [{ $strcasecmp : ['$A', '$B'] }, -1] } } }", new int[] { 2, 5 })] + [InlineData(2, true, "{ $match : { $expr : { $eq : [{ $strcasecmp : ['$A', '$B'] }, 0] } } }", new int[] { 1, 4 })] + [InlineData(3, true, "{ $match : { $expr : { $eq : [{ $strcasecmp : ['$A', '$B'] }, 1] } } }", new int[] { 3, 6 })] + [InlineData(4, true, "{ $match : { $expr : { $ne : [{ $strcasecmp : ['$A', '$B'] }, -1] } } }", new int[] { 1, 3, 4, 6 })] + [InlineData(5, true, "{ $match : { $expr : { $ne : [{ $strcasecmp : ['$A', '$B'] }, 0] } } }", new int[] { 2, 3, 5, 6 })] + [InlineData(6, true, "{ $match : { $expr : { $ne : [{ $strcasecmp : ['$A', '$B'] }, 1] } } }", new int[] { 1, 2, 4, 5 })] + [InlineData(7, true, "{ $match : { $expr : { $gt : [{ $strcasecmp : ['$A', '$B'] }, -1] } } }", new int[] { 1, 3, 4, 6 })] + [InlineData(8, true, "{ $match : { $expr : { $gt : [{ $strcasecmp : ['$A', '$B'] }, 0] } } }", new int[] { 3, 6 })] + [InlineData(9, true, "{ $match : { $expr : { $lt : [{ $strcasecmp : ['$A', '$B'] }, 0] } } }", new int[] { 2, 5 })] + [InlineData(10, true, "{ $match : { $expr : { $lt : [{ $strcasecmp : ['$A', '$B'] }, 1] } } }", new int[] { 1, 2, 4, 5 })] + [InlineData(11, true, "{ $match : { $expr : { $lte : [{ $strcasecmp : ['$A', '$B'] }, 0] } } }", new int[] { 1, 2, 4, 5 })] + [InlineData(12, true, "{ $match : { $expr : { $gte : [{ $strcasecmp : ['$A', '$B'] }, 0] } } }", new int[] { 1, 3, 4, 6 })] + public void Where_String_Compare_field_to_field_with_ignoreCase_should_work(int scenario, bool ignoreCase, string expectedStage, int[] expectedIds) + { + var collection = Fixture.Collection; + + var queryable = scenario switch + { + // Compare field to constant + 1 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B, ignoreCase) == -1), + 2 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B, ignoreCase) == 0), + 3 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B, ignoreCase) == 1), + 4 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B, ignoreCase) != -1), + 5 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B, ignoreCase) != 0), + 6 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B, ignoreCase) != 1), + 7 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B, ignoreCase) > -1), + 8 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B, ignoreCase) > 0), + 9 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B, ignoreCase) < 0), + 10 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B, ignoreCase) < 1), + 11 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B, ignoreCase) <= 0), + 12 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B, ignoreCase) >= 0), + _ => throw new ArgumentException($"Invalid scenario: {scenario}.") + }; + + AssertIds(collection, queryable, expectedStage, expectedIds); + } + private void Assert(IMongoCollection collection, IQueryable queryable, string expectedStage, TResult[] expectedResults) { var stages = Translate(collection, queryable); From b429c2e7c14a847712049013d0e5fd24ac34b38e Mon Sep 17 00:00:00 2001 From: rstam Date: Wed, 22 Oct 2025 09:51:45 -0400 Subject: [PATCH 7/9] CSHARP-5730: Minor cleanup before requesting re-review. --- .../Ast/Filters/AstFilter.cs | 20 ------------------- ...eComparisonExpressionToFilterTranslator.cs | 1 - 2 files changed, 21 deletions(-) diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Filters/AstFilter.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Filters/AstFilter.cs index f9c68917937..ab516eed243 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Filters/AstFilter.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Filters/AstFilter.cs @@ -139,31 +139,11 @@ public static AstFilterField Field(string path) return new AstFilterField(path); } - public static AstFieldOperationFilter Gt(AstFilterField field, BsonValue value) - { - return new AstFieldOperationFilter(field, new AstComparisonFilterOperation(AstComparisonFilterOperator.Gt, value)); - } - - public static AstFieldOperationFilter Gte(AstFilterField field, BsonValue value) - { - return new AstFieldOperationFilter(field, new AstComparisonFilterOperation(AstComparisonFilterOperator.Gte, value)); - } - public static AstFieldOperationFilter In(AstFilterField field, IEnumerable values) { return new AstFieldOperationFilter(field, new AstInFilterOperation(values)); } - public static AstFieldOperationFilter Lt(AstFilterField field, BsonValue value) - { - return new AstFieldOperationFilter(field, new AstComparisonFilterOperation(AstComparisonFilterOperator.Lt, value)); - } - - public static AstFieldOperationFilter Lte(AstFilterField field, BsonValue value) - { - return new AstFieldOperationFilter(field, new AstComparisonFilterOperation(AstComparisonFilterOperator.Lte, value)); - } - public static AstFilter MatchesEverything() { return new AstMatchesEverythingFilter(); diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ExpressionTranslators/CompareComparisonExpressionToFilterTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ExpressionTranslators/CompareComparisonExpressionToFilterTranslator.cs index 6758a9cfb6d..2c67478acc9 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ExpressionTranslators/CompareComparisonExpressionToFilterTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ExpressionTranslators/CompareComparisonExpressionToFilterTranslator.cs @@ -14,7 +14,6 @@ */ 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; From bc8cb718dfbe36c7360446c2572da7041accb880 Mon Sep 17 00:00:00 2001 From: rstam Date: Thu, 23 Oct 2025 10:34:29 -0400 Subject: [PATCH 8/9] CSHARP-5730: Added tests for CompareTo. --- .../Jira/CSharp5730Tests.cs | 769 ++++++++++++++---- 1 file changed, 607 insertions(+), 162 deletions(-) diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5730Tests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5730Tests.cs index 4a42eadf2d5..f49ddcdce13 100644 --- a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5730Tests.cs +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5730Tests.cs @@ -30,37 +30,111 @@ public CSharp5730Tests(ClassFixture fixture) } [Theory] - [InlineData(1, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] - [InlineData(2, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] - [InlineData(3, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { false, false, false, false, false, false })] - [InlineData(4, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] - [InlineData(5, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] - [InlineData(6, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] - [InlineData(7, "{ $project : { _v : { $gt : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] - [InlineData(8, "{ $project : { _v : { $gt : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, false, false, false, false, false })] - [InlineData(9, "{ $project : { _v : { $lt : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] - [InlineData(10, "{ $project : { _v : { $lt : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] - [InlineData(11, "{ $project : { _v : { $lte : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] - [InlineData(12, "{ $project : { _v : { $gte : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] + [InlineData(101, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] + [InlineData(102, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] + [InlineData(103, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { false, false, false, false, false, false })] + [InlineData(104, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] + [InlineData(105, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] + [InlineData(106, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] + [InlineData(107, "{ $project : { _v : { $gt : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] + [InlineData(108, "{ $project : { _v : { $gt : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, false, false, false, false, false })] + [InlineData(109, "{ $project : { _v : { $lt : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] + [InlineData(110, "{ $project : { _v : { $lt : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] + [InlineData(111, "{ $project : { _v : { $lte : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] + [InlineData(112, "{ $project : { _v : { $gte : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] + [InlineData(201, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] + [InlineData(202, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] + [InlineData(203, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { false, false, false, false, false, false })] + [InlineData(204, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] + [InlineData(205, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] + [InlineData(206, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] + [InlineData(207, "{ $project : { _v : { $gt : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] + [InlineData(208, "{ $project : { _v : { $gt : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, false, false, false, false, false })] + [InlineData(209, "{ $project : { _v : { $lt : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] + [InlineData(210, "{ $project : { _v : { $lt : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] + [InlineData(211, "{ $project : { _v : { $lte : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] + [InlineData(212, "{ $project : { _v : { $gte : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] + [InlineData(301, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] + [InlineData(302, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] + [InlineData(303, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { false, false, false, false, false, false })] + [InlineData(304, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] + [InlineData(305, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] + [InlineData(306, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] + [InlineData(307, "{ $project : { _v : { $gt : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] + [InlineData(308, "{ $project : { _v : { $gt : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, false, false, false, false, false })] + [InlineData(309, "{ $project : { _v : { $lt : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] + [InlineData(310, "{ $project : { _v : { $lt : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] + [InlineData(311, "{ $project : { _v : { $lte : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] + [InlineData(312, "{ $project : { _v : { $gte : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] + [InlineData(401, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] + [InlineData(402, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] + [InlineData(403, "{ $project : { _v : { $eq : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { false, false, false, false, false, false })] + [InlineData(404, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] + [InlineData(405, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] + [InlineData(406, "{ $project : { _v : { $ne : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] + [InlineData(407, "{ $project : { _v : { $gt : [{ $cmp : ['A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] + [InlineData(408, "{ $project : { _v : { $gt : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, false, false, false, false, false })] + [InlineData(409, "{ $project : { _v : { $lt : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, true, true, true })] + [InlineData(410, "{ $project : { _v : { $lt : [{ $cmp : ['A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] + [InlineData(411, "{ $project : { _v : { $lte : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, true, true, true, true, true })] + [InlineData(412, "{ $project : { _v : { $gte : [{ $cmp : ['A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, false, false, false })] public void Select_String_Compare_constant_to_field_should_work(int scenario, string expectedStage, bool[] expectedResults) { var collection = Fixture.Collection; var queryable = scenario switch { - // Compare field to constant - 1 => collection.AsQueryable().Select(x => string.Compare("A", x.B) == -1), - 2 => collection.AsQueryable().Select(x => string.Compare("A", x.B) == 0), - 3 => collection.AsQueryable().Select(x => string.Compare("A", x.B) == 1), - 4 => collection.AsQueryable().Select(x => string.Compare("A", x.B) != -1), - 5 => collection.AsQueryable().Select(x => string.Compare("A", x.B) != 0), - 6 => collection.AsQueryable().Select(x => string.Compare("A", x.B) != 1), - 7 => collection.AsQueryable().Select(x => string.Compare("A", x.B) > -1), - 8 => collection.AsQueryable().Select(x => string.Compare("A", x.B) > 0), - 9 => collection.AsQueryable().Select(x => string.Compare("A", x.B) < 0), - 10 => collection.AsQueryable().Select(x => string.Compare("A", x.B) < 1), - 11 => collection.AsQueryable().Select(x => string.Compare("A", x.B) <= 0), - 12 => collection.AsQueryable().Select(x => string.Compare("A", x.B) >= 0), + 101 => collection.AsQueryable().Select(x => String.Compare("A", x.B) == -1), + 102 => collection.AsQueryable().Select(x => String.Compare("A", x.B) == 0), + 103 => collection.AsQueryable().Select(x => String.Compare("A", x.B) == 1), + 104 => collection.AsQueryable().Select(x => String.Compare("A", x.B) != -1), + 105 => collection.AsQueryable().Select(x => String.Compare("A", x.B) != 0), + 106 => collection.AsQueryable().Select(x => String.Compare("A", x.B) != 1), + 107 => collection.AsQueryable().Select(x => String.Compare("A", x.B) > -1), + 108 => collection.AsQueryable().Select(x => String.Compare("A", x.B) > 0), + 109 => collection.AsQueryable().Select(x => String.Compare("A", x.B) < 0), + 110 => collection.AsQueryable().Select(x => String.Compare("A", x.B) < 1), + 111 => collection.AsQueryable().Select(x => String.Compare("A", x.B) <= 0), + 112 => collection.AsQueryable().Select(x => String.Compare("A", x.B) >= 0), + + 201 => collection.AsQueryable().Select(x => "A".CompareTo(x.B) == -1), + 202 => collection.AsQueryable().Select(x => "A".CompareTo(x.B) == 0), + 203 => collection.AsQueryable().Select(x => "A".CompareTo(x.B) == 1), + 204 => collection.AsQueryable().Select(x => "A".CompareTo(x.B) != -1), + 205 => collection.AsQueryable().Select(x => "A".CompareTo(x.B) != 0), + 206 => collection.AsQueryable().Select(x => "A".CompareTo(x.B) != 1), + 207 => collection.AsQueryable().Select(x => "A".CompareTo(x.B) > -1), + 208 => collection.AsQueryable().Select(x => "A".CompareTo(x.B) > 0), + 209 => collection.AsQueryable().Select(x => "A".CompareTo(x.B) < 0), + 210 => collection.AsQueryable().Select(x => "A".CompareTo(x.B) < 1), + 211 => collection.AsQueryable().Select(x => "A".CompareTo(x.B) <= 0), + 212 => collection.AsQueryable().Select(x => "A".CompareTo(x.B) >= 0), + + 301 => collection.AsQueryable().Select(x => ((IComparable)"A").CompareTo(x.B) == -1), + 302 => collection.AsQueryable().Select(x => ((IComparable)"A").CompareTo(x.B) == 0), + 303 => collection.AsQueryable().Select(x => ((IComparable)"A").CompareTo(x.B) == 1), + 304 => collection.AsQueryable().Select(x => ((IComparable)"A").CompareTo(x.B) != -1), + 305 => collection.AsQueryable().Select(x => ((IComparable)"A").CompareTo(x.B) != 0), + 306 => collection.AsQueryable().Select(x => ((IComparable)"A").CompareTo(x.B) != 1), + 307 => collection.AsQueryable().Select(x => ((IComparable)"A").CompareTo(x.B) > -1), + 308 => collection.AsQueryable().Select(x => ((IComparable)"A").CompareTo(x.B) > 0), + 309 => collection.AsQueryable().Select(x => ((IComparable)"A").CompareTo(x.B) < 0), + 310 => collection.AsQueryable().Select(x => ((IComparable)"A").CompareTo(x.B) < 1), + 311 => collection.AsQueryable().Select(x => ((IComparable)"A").CompareTo(x.B) <= 0), + 312 => collection.AsQueryable().Select(x => ((IComparable)"A").CompareTo(x.B) >= 0), + + 401 => collection.AsQueryable().Select(x => ((IComparable)"A").CompareTo(x.B) == -1), + 402 => collection.AsQueryable().Select(x => ((IComparable)"A").CompareTo(x.B) == 0), + 403 => collection.AsQueryable().Select(x => ((IComparable)"A").CompareTo(x.B) == 1), + 404 => collection.AsQueryable().Select(x => ((IComparable)"A").CompareTo(x.B) != -1), + 405 => collection.AsQueryable().Select(x => ((IComparable)"A").CompareTo(x.B) != 0), + 406 => collection.AsQueryable().Select(x => ((IComparable)"A").CompareTo(x.B) != 1), + 407 => collection.AsQueryable().Select(x => ((IComparable)"A").CompareTo(x.B) > -1), + 408 => collection.AsQueryable().Select(x => ((IComparable)"A").CompareTo(x.B) > 0), + 409 => collection.AsQueryable().Select(x => ((IComparable)"A").CompareTo(x.B) < 0), + 410 => collection.AsQueryable().Select(x => ((IComparable)"A").CompareTo(x.B) < 1), + 411 => collection.AsQueryable().Select(x => ((IComparable)"A").CompareTo(x.B) <= 0), + 412 => collection.AsQueryable().Select(x => ((IComparable)"A").CompareTo(x.B) >= 0), _ => throw new ArgumentException($"Invalid scenario: {scenario}.") }; @@ -98,7 +172,6 @@ public void Select_String_Compare_constant_to_field_with_ignoreCase_should_work( var queryable = scenario switch { - // Compare field to constant 1 => collection.AsQueryable().Select(x => string.Compare("A", x.B, ignoreCase) == -1), 2 => collection.AsQueryable().Select(x => string.Compare("A", x.B, ignoreCase) == 0), 3 => collection.AsQueryable().Select(x => string.Compare("A", x.B, ignoreCase) == 1), @@ -118,37 +191,112 @@ public void Select_String_Compare_constant_to_field_with_ignoreCase_should_work( } [Theory] - [InlineData(1, "{ $project : { _v : { $eq : [{ $cmp : ['$A', 'B'] }, -1] }, _id : 0 } }", new bool[] { true, true, false, false, false, false })] - [InlineData(2, "{ $project : { _v : { $eq : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, false, false, false })] - [InlineData(3, "{ $project : { _v : { $eq : [{ $cmp : ['$A', 'B'] }, 1] }, _id : 0 } }", new bool[] { false, false, false, true, true, true })] - [InlineData(4, "{ $project : { _v : { $ne : [{ $cmp : ['$A', 'B'] }, -1] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] - [InlineData(5, "{ $project : { _v : { $ne : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, true, true, true })] - [InlineData(6, "{ $project : { _v : { $ne : [{ $cmp : ['$A', 'B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] - [InlineData(7, "{ $project : { _v : { $gt : [{ $cmp : ['$A', 'B'] }, -1] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] - [InlineData(8, "{ $project : { _v : { $gt : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, false, true, true, true })] - [InlineData(9, "{ $project : { _v : { $lt : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, false, false, false })] - [InlineData(10, "{ $project : { _v : { $lt : [{ $cmp : ['$A', 'B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] - [InlineData(11, "{ $project : { _v : { $lte : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] - [InlineData(12, "{ $project : { _v : { $gte : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] + [InlineData(101, "{ $project : { _v : { $eq : [{ $cmp : ['$A', 'B'] }, -1] }, _id : 0 } }", new bool[] { true, true, false, false, false, false })] + [InlineData(102, "{ $project : { _v : { $eq : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, false, false, false })] + [InlineData(103, "{ $project : { _v : { $eq : [{ $cmp : ['$A', 'B'] }, 1] }, _id : 0 } }", new bool[] { false, false, false, true, true, true })] + [InlineData(104, "{ $project : { _v : { $ne : [{ $cmp : ['$A', 'B'] }, -1] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] + [InlineData(105, "{ $project : { _v : { $ne : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, true, true, true })] + [InlineData(106, "{ $project : { _v : { $ne : [{ $cmp : ['$A', 'B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] + [InlineData(107, "{ $project : { _v : { $gt : [{ $cmp : ['$A', 'B'] }, -1] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] + [InlineData(108, "{ $project : { _v : { $gt : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, false, true, true, true })] + [InlineData(109, "{ $project : { _v : { $lt : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, false, false, false })] + [InlineData(110, "{ $project : { _v : { $lt : [{ $cmp : ['$A', 'B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] + [InlineData(111, "{ $project : { _v : { $lte : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] + [InlineData(112, "{ $project : { _v : { $gte : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] + [InlineData(201, "{ $project : { _v : { $eq : [{ $cmp : ['$A', 'B'] }, -1] }, _id : 0 } }", new bool[] { true, true, false, false, false, false })] + [InlineData(202, "{ $project : { _v : { $eq : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, false, false, false })] + [InlineData(203, "{ $project : { _v : { $eq : [{ $cmp : ['$A', 'B'] }, 1] }, _id : 0 } }", new bool[] { false, false, false, true, true, true })] + [InlineData(204, "{ $project : { _v : { $ne : [{ $cmp : ['$A', 'B'] }, -1] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] + [InlineData(205, "{ $project : { _v : { $ne : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, true, true, true })] + [InlineData(206, "{ $project : { _v : { $ne : [{ $cmp : ['$A', 'B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] + [InlineData(207, "{ $project : { _v : { $gt : [{ $cmp : ['$A', 'B'] }, -1] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] + [InlineData(208, "{ $project : { _v : { $gt : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, false, true, true, true })] + [InlineData(209, "{ $project : { _v : { $lt : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, false, false, false })] + [InlineData(210, "{ $project : { _v : { $lt : [{ $cmp : ['$A', 'B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] + [InlineData(211, "{ $project : { _v : { $lte : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] + [InlineData(212, "{ $project : { _v : { $gte : [{ $cmp : ['$A', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] + [InlineData(301, "{ $project : { _v : { $eq : [{ $cmp : ['$ICA', 'B'] }, -1] }, _id : 0 } }", new bool[] { true, true, false, false, false, false })] + [InlineData(302, "{ $project : { _v : { $eq : [{ $cmp : ['$ICA', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, false, false, false })] + [InlineData(303, "{ $project : { _v : { $eq : [{ $cmp : ['$ICA', 'B'] }, 1] }, _id : 0 } }", new bool[] { false, false, false, true, true, true })] + [InlineData(304, "{ $project : { _v : { $ne : [{ $cmp : ['$ICA', 'B'] }, -1] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] + [InlineData(305, "{ $project : { _v : { $ne : [{ $cmp : ['$ICA', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, true, true, true })] + [InlineData(306, "{ $project : { _v : { $ne : [{ $cmp : ['$ICA', 'B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] + [InlineData(307, "{ $project : { _v : { $gt : [{ $cmp : ['$ICA', 'B'] }, -1] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] + [InlineData(308, "{ $project : { _v : { $gt : [{ $cmp : ['$ICA', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, false, true, true, true })] + [InlineData(309, "{ $project : { _v : { $lt : [{ $cmp : ['$ICA', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, false, false, false })] + [InlineData(310, "{ $project : { _v : { $lt : [{ $cmp : ['$ICA', 'B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] + [InlineData(311, "{ $project : { _v : { $lte : [{ $cmp : ['$ICA', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] + [InlineData(312, "{ $project : { _v : { $gte : [{ $cmp : ['$ICA', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] + [InlineData(401, "{ $project : { _v : { $eq : [{ $cmp : ['$ICSA', 'B'] }, -1] }, _id : 0 } }", new bool[] { true, true, false, false, false, false })] + [InlineData(402, "{ $project : { _v : { $eq : [{ $cmp : ['$ICSA', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, false, false, false })] + [InlineData(403, "{ $project : { _v : { $eq : [{ $cmp : ['$ICSA', 'B'] }, 1] }, _id : 0 } }", new bool[] { false, false, false, true, true, true })] + [InlineData(404, "{ $project : { _v : { $ne : [{ $cmp : ['$ICSA', 'B'] }, -1] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] + [InlineData(405, "{ $project : { _v : { $ne : [{ $cmp : ['$ICSA', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, true, true, true })] + [InlineData(406, "{ $project : { _v : { $ne : [{ $cmp : ['$ICSA', 'B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] + [InlineData(407, "{ $project : { _v : { $gt : [{ $cmp : ['$ICSA', 'B'] }, -1] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] + [InlineData(408, "{ $project : { _v : { $gt : [{ $cmp : ['$ICSA', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, false, true, true, true })] + [InlineData(409, "{ $project : { _v : { $lt : [{ $cmp : ['$ICSA', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, false, false, false })] + [InlineData(410, "{ $project : { _v : { $lt : [{ $cmp : ['$ICSA', 'B'] }, 1] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] + [InlineData(411, "{ $project : { _v : { $lte : [{ $cmp : ['$ICSA', 'B'] }, 0] }, _id : 0 } }", new bool[] { true, true, true, false, false, false })] + [InlineData(412, "{ $project : { _v : { $gte : [{ $cmp : ['$ICSA', 'B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, true, true, true })] public void Select_String_Compare_field_to_constant_should_work(int scenario, string expectedStage, bool[] expectedResults) { var collection = Fixture.Collection; var queryable = scenario switch { - // Compare field to constant - 1 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") == -1), - 2 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") == 0), - 3 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") == 1), - 4 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") != -1), - 5 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") != 0), - 6 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") != 1), - 7 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") > -1), - 8 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") > 0), - 9 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") < 0), - 10 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") < 1), - 11 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") <= 0), - 12 => collection.AsQueryable().Select(x => string.Compare(x.A, "B") >= 0), + 101 => collection.AsQueryable().Select(x => String.Compare(x.A, "B") == -1), + 102 => collection.AsQueryable().Select(x => String.Compare(x.A, "B") == 0), + 103 => collection.AsQueryable().Select(x => String.Compare(x.A, "B") == 1), + 104 => collection.AsQueryable().Select(x => String.Compare(x.A, "B") != -1), + 105 => collection.AsQueryable().Select(x => String.Compare(x.A, "B") != 0), + 106 => collection.AsQueryable().Select(x => String.Compare(x.A, "B") != 1), + 107 => collection.AsQueryable().Select(x => String.Compare(x.A, "B") > -1), + 108 => collection.AsQueryable().Select(x => String.Compare(x.A, "B") > 0), + 109 => collection.AsQueryable().Select(x => String.Compare(x.A, "B") < 0), + 110 => collection.AsQueryable().Select(x => String.Compare(x.A, "B") < 1), + 111 => collection.AsQueryable().Select(x => String.Compare(x.A, "B") <= 0), + 112 => collection.AsQueryable().Select(x => String.Compare(x.A, "B") >= 0), + + 201 => collection.AsQueryable().Select(x => x.A.CompareTo("B") == -1), + 202 => collection.AsQueryable().Select(x => x.A.CompareTo("B") == 0), + 203 => collection.AsQueryable().Select(x => x.A.CompareTo("B") == 1), + 204 => collection.AsQueryable().Select(x => x.A.CompareTo("B") != -1), + 205 => collection.AsQueryable().Select(x => x.A.CompareTo("B") != 0), + 206 => collection.AsQueryable().Select(x => x.A.CompareTo("B") != 1), + 207 => collection.AsQueryable().Select(x => x.A.CompareTo("B") > -1), + 208 => collection.AsQueryable().Select(x => x.A.CompareTo("B") > 0), + 209 => collection.AsQueryable().Select(x => x.A.CompareTo("B") < 0), + 210 => collection.AsQueryable().Select(x => x.A.CompareTo("B") < 1), + 211 => collection.AsQueryable().Select(x => x.A.CompareTo("B") <= 0), + 212 => collection.AsQueryable().Select(x => x.A.CompareTo("B") >= 0), + + 301 => collection.AsQueryable().Select(x => x.ICA.CompareTo("B") == -1), + 302 => collection.AsQueryable().Select(x => x.ICA.CompareTo("B") == 0), + 303 => collection.AsQueryable().Select(x => x.ICA.CompareTo("B") == 1), + 304 => collection.AsQueryable().Select(x => x.ICA.CompareTo("B") != -1), + 305 => collection.AsQueryable().Select(x => x.ICA.CompareTo("B") != 0), + 306 => collection.AsQueryable().Select(x => x.ICA.CompareTo("B") != 1), + 307 => collection.AsQueryable().Select(x => x.ICA.CompareTo("B") > -1), + 308 => collection.AsQueryable().Select(x => x.ICA.CompareTo("B") > 0), + 309 => collection.AsQueryable().Select(x => x.ICA.CompareTo("B") < 0), + 310 => collection.AsQueryable().Select(x => x.ICA.CompareTo("B") < 1), + 311 => collection.AsQueryable().Select(x => x.ICA.CompareTo("B") <= 0), + 312 => collection.AsQueryable().Select(x => x.ICA.CompareTo("B") >= 0), + + 401 => collection.AsQueryable().Select(x => x.ICSA.CompareTo("B") == -1), + 402 => collection.AsQueryable().Select(x => x.ICSA.CompareTo("B") == 0), + 403 => collection.AsQueryable().Select(x => x.ICSA.CompareTo("B") == 1), + 404 => collection.AsQueryable().Select(x => x.ICSA.CompareTo("B") != -1), + 405 => collection.AsQueryable().Select(x => x.ICSA.CompareTo("B") != 0), + 406 => collection.AsQueryable().Select(x => x.ICSA.CompareTo("B") != 1), + 407 => collection.AsQueryable().Select(x => x.ICSA.CompareTo("B") > -1), + 408 => collection.AsQueryable().Select(x => x.ICSA.CompareTo("B") > 0), + 409 => collection.AsQueryable().Select(x => x.ICSA.CompareTo("B") < 0), + 410 => collection.AsQueryable().Select(x => x.ICSA.CompareTo("B") < 1), + 411 => collection.AsQueryable().Select(x => x.ICSA.CompareTo("B") <= 0), + 412 => collection.AsQueryable().Select(x => x.ICSA.CompareTo("B") >= 0), + _ => throw new ArgumentException($"Invalid scenario: {scenario}.") }; @@ -186,7 +334,6 @@ public void Select_String_Compare_field_to_constant_with_ignoreCase_should_work( var queryable = scenario switch { - // Compare field to constant 1 => collection.AsQueryable().Select(x => string.Compare(x.A, "B", ignoreCase) == -1), 2 => collection.AsQueryable().Select(x => string.Compare(x.A, "B", ignoreCase) == 0), 3 => collection.AsQueryable().Select(x => string.Compare(x.A, "B", ignoreCase) == 1), @@ -206,37 +353,112 @@ public void Select_String_Compare_field_to_constant_with_ignoreCase_should_work( } [Theory] - [InlineData(1, "{ $project : { _v : { $eq : [{ $cmp : ['$A', '$B'] }, -1] }, _id : 0 } }", new bool[] { false, true, false, false, true, false })] - [InlineData(2, "{ $project : { _v : { $eq : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, false, true, false, false })] - [InlineData(3, "{ $project : { _v : { $eq : [{ $cmp : ['$A', '$B'] }, 1] }, _id : 0 } }", new bool[] { false, false, true, false, false, true })] - [InlineData(4, "{ $project : { _v : { $ne : [{ $cmp : ['$A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] - [InlineData(5, "{ $project : { _v : { $ne : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, true, false, true, true })] - [InlineData(6, "{ $project : { _v : { $ne : [{ $cmp : ['$A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] - [InlineData(7, "{ $project : { _v : { $gt : [{ $cmp : ['$A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] - [InlineData(8, "{ $project : { _v : { $gt : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, false, false, true })] - [InlineData(9, "{ $project : { _v : { $lt : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, false, true, false })] - [InlineData(10, "{ $project : { _v : { $lt : [{ $cmp : ['$A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] - [InlineData(11, "{ $project : { _v : { $lte : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] - [InlineData(12, "{ $project : { _v : { $gte : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] + [InlineData(101, "{ $project : { _v : { $eq : [{ $cmp : ['$A', '$B'] }, -1] }, _id : 0 } }", new bool[] { false, true, false, false, true, false })] + [InlineData(102, "{ $project : { _v : { $eq : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, false, true, false, false })] + [InlineData(103, "{ $project : { _v : { $eq : [{ $cmp : ['$A', '$B'] }, 1] }, _id : 0 } }", new bool[] { false, false, true, false, false, true })] + [InlineData(104, "{ $project : { _v : { $ne : [{ $cmp : ['$A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] + [InlineData(105, "{ $project : { _v : { $ne : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, true, false, true, true })] + [InlineData(106, "{ $project : { _v : { $ne : [{ $cmp : ['$A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] + [InlineData(107, "{ $project : { _v : { $gt : [{ $cmp : ['$A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] + [InlineData(108, "{ $project : { _v : { $gt : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, false, false, true })] + [InlineData(109, "{ $project : { _v : { $lt : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, false, true, false })] + [InlineData(110, "{ $project : { _v : { $lt : [{ $cmp : ['$A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] + [InlineData(111, "{ $project : { _v : { $lte : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] + [InlineData(112, "{ $project : { _v : { $gte : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] + [InlineData(201, "{ $project : { _v : { $eq : [{ $cmp : ['$A', '$B'] }, -1] }, _id : 0 } }", new bool[] { false, true, false, false, true, false })] + [InlineData(202, "{ $project : { _v : { $eq : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, false, true, false, false })] + [InlineData(203, "{ $project : { _v : { $eq : [{ $cmp : ['$A', '$B'] }, 1] }, _id : 0 } }", new bool[] { false, false, true, false, false, true })] + [InlineData(204, "{ $project : { _v : { $ne : [{ $cmp : ['$A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] + [InlineData(205, "{ $project : { _v : { $ne : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, true, false, true, true })] + [InlineData(206, "{ $project : { _v : { $ne : [{ $cmp : ['$A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] + [InlineData(207, "{ $project : { _v : { $gt : [{ $cmp : ['$A', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] + [InlineData(208, "{ $project : { _v : { $gt : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, false, false, true })] + [InlineData(209, "{ $project : { _v : { $lt : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, false, true, false })] + [InlineData(210, "{ $project : { _v : { $lt : [{ $cmp : ['$A', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] + [InlineData(211, "{ $project : { _v : { $lte : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] + [InlineData(212, "{ $project : { _v : { $gte : [{ $cmp : ['$A', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] + [InlineData(301, "{ $project : { _v : { $eq : [{ $cmp : ['$ICA', '$B'] }, -1] }, _id : 0 } }", new bool[] { false, true, false, false, true, false })] + [InlineData(302, "{ $project : { _v : { $eq : [{ $cmp : ['$ICA', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, false, true, false, false })] + [InlineData(303, "{ $project : { _v : { $eq : [{ $cmp : ['$ICA', '$B'] }, 1] }, _id : 0 } }", new bool[] { false, false, true, false, false, true })] + [InlineData(304, "{ $project : { _v : { $ne : [{ $cmp : ['$ICA', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] + [InlineData(305, "{ $project : { _v : { $ne : [{ $cmp : ['$ICA', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, true, false, true, true })] + [InlineData(306, "{ $project : { _v : { $ne : [{ $cmp : ['$ICA', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] + [InlineData(307, "{ $project : { _v : { $gt : [{ $cmp : ['$ICA', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] + [InlineData(308, "{ $project : { _v : { $gt : [{ $cmp : ['$ICA', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, false, false, true })] + [InlineData(309, "{ $project : { _v : { $lt : [{ $cmp : ['$ICA', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, false, true, false })] + [InlineData(310, "{ $project : { _v : { $lt : [{ $cmp : ['$ICA', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] + [InlineData(311, "{ $project : { _v : { $lte : [{ $cmp : ['$ICA', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] + [InlineData(312, "{ $project : { _v : { $gte : [{ $cmp : ['$ICA', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] + [InlineData(401, "{ $project : { _v : { $eq : [{ $cmp : ['$ICSA', '$B'] }, -1] }, _id : 0 } }", new bool[] { false, true, false, false, true, false })] + [InlineData(402, "{ $project : { _v : { $eq : [{ $cmp : ['$ICSA', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, false, true, false, false })] + [InlineData(403, "{ $project : { _v : { $eq : [{ $cmp : ['$ICSA', '$B'] }, 1] }, _id : 0 } }", new bool[] { false, false, true, false, false, true })] + [InlineData(404, "{ $project : { _v : { $ne : [{ $cmp : ['$ICSA', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] + [InlineData(405, "{ $project : { _v : { $ne : [{ $cmp : ['$ICSA', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, true, false, true, true })] + [InlineData(406, "{ $project : { _v : { $ne : [{ $cmp : ['$ICSA', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] + [InlineData(407, "{ $project : { _v : { $gt : [{ $cmp : ['$ICSA', '$B'] }, -1] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] + [InlineData(408, "{ $project : { _v : { $gt : [{ $cmp : ['$ICSA', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, false, true, false, false, true })] + [InlineData(409, "{ $project : { _v : { $lt : [{ $cmp : ['$ICSA', '$B'] }, 0] }, _id : 0 } }", new bool[] { false, true, false, false, true, false })] + [InlineData(410, "{ $project : { _v : { $lt : [{ $cmp : ['$ICSA', '$B'] }, 1] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] + [InlineData(411, "{ $project : { _v : { $lte : [{ $cmp : ['$ICSA', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, true, false, true, true, false })] + [InlineData(412, "{ $project : { _v : { $gte : [{ $cmp : ['$ICSA', '$B'] }, 0] }, _id : 0 } }", new bool[] { true, false, true, true, false, true })] public void Select_String_Compare_field_to_field_should_work(int scenario, string expectedStage, bool[] expectedResults) { var collection = Fixture.Collection; var queryable = scenario switch { - // Compare field to constant - 1 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B) == -1), - 2 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B) == 0), - 3 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B) == 1), - 4 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B) != -1), - 5 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B) != 0), - 6 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B) != 1), - 7 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B) > -1), - 8 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B) > 0), - 9 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B) < 0), - 10 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B) < 1), - 11 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B) <= 0), - 12 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B) >= 0), + 101 => collection.AsQueryable().Select(x => String.Compare(x.A, x.B) == -1), + 102 => collection.AsQueryable().Select(x => String.Compare(x.A, x.B) == 0), + 103 => collection.AsQueryable().Select(x => String.Compare(x.A, x.B) == 1), + 104 => collection.AsQueryable().Select(x => String.Compare(x.A, x.B) != -1), + 105 => collection.AsQueryable().Select(x => String.Compare(x.A, x.B) != 0), + 106 => collection.AsQueryable().Select(x => String.Compare(x.A, x.B) != 1), + 107 => collection.AsQueryable().Select(x => String.Compare(x.A, x.B) > -1), + 108 => collection.AsQueryable().Select(x => String.Compare(x.A, x.B) > 0), + 109 => collection.AsQueryable().Select(x => String.Compare(x.A, x.B) < 0), + 110 => collection.AsQueryable().Select(x => String.Compare(x.A, x.B) < 1), + 111 => collection.AsQueryable().Select(x => String.Compare(x.A, x.B) <= 0), + 112 => collection.AsQueryable().Select(x => String.Compare(x.A, x.B) >= 0), + + 201 => collection.AsQueryable().Select(x => x.A.CompareTo(x.B) == -1), + 202 => collection.AsQueryable().Select(x => x.A.CompareTo(x.B) == 0), + 203 => collection.AsQueryable().Select(x => x.A.CompareTo(x.B) == 1), + 204 => collection.AsQueryable().Select(x => x.A.CompareTo(x.B) != -1), + 205 => collection.AsQueryable().Select(x => x.A.CompareTo(x.B) != 0), + 206 => collection.AsQueryable().Select(x => x.A.CompareTo(x.B) != 1), + 207 => collection.AsQueryable().Select(x => x.A.CompareTo(x.B) > -1), + 208 => collection.AsQueryable().Select(x => x.A.CompareTo(x.B) > 0), + 209 => collection.AsQueryable().Select(x => x.A.CompareTo(x.B) < 0), + 210 => collection.AsQueryable().Select(x => x.A.CompareTo(x.B) < 1), + 211 => collection.AsQueryable().Select(x => x.A.CompareTo(x.B) <= 0), + 212 => collection.AsQueryable().Select(x => x.A.CompareTo(x.B) >= 0), + + 301 => collection.AsQueryable().Select(x => x.ICA.CompareTo(x.B) == -1), + 302 => collection.AsQueryable().Select(x => x.ICA.CompareTo(x.B) == 0), + 303 => collection.AsQueryable().Select(x => x.ICA.CompareTo(x.B) == 1), + 304 => collection.AsQueryable().Select(x => x.ICA.CompareTo(x.B) != -1), + 305 => collection.AsQueryable().Select(x => x.ICA.CompareTo(x.B) != 0), + 306 => collection.AsQueryable().Select(x => x.ICA.CompareTo(x.B) != 1), + 307 => collection.AsQueryable().Select(x => x.ICA.CompareTo(x.B) > -1), + 308 => collection.AsQueryable().Select(x => x.ICA.CompareTo(x.B) > 0), + 309 => collection.AsQueryable().Select(x => x.ICA.CompareTo(x.B) < 0), + 310 => collection.AsQueryable().Select(x => x.ICA.CompareTo(x.B) < 1), + 311 => collection.AsQueryable().Select(x => x.ICA.CompareTo(x.B) <= 0), + 312 => collection.AsQueryable().Select(x => x.ICA.CompareTo(x.B) >= 0), + + 401 => collection.AsQueryable().Select(x => x.ICSA.CompareTo(x.B) == -1), + 402 => collection.AsQueryable().Select(x => x.ICSA.CompareTo(x.B) == 0), + 403 => collection.AsQueryable().Select(x => x.ICSA.CompareTo(x.B) == 1), + 404 => collection.AsQueryable().Select(x => x.ICSA.CompareTo(x.B) != -1), + 405 => collection.AsQueryable().Select(x => x.ICSA.CompareTo(x.B) != 0), + 406 => collection.AsQueryable().Select(x => x.ICSA.CompareTo(x.B) != 1), + 407 => collection.AsQueryable().Select(x => x.ICSA.CompareTo(x.B) > -1), + 408 => collection.AsQueryable().Select(x => x.ICSA.CompareTo(x.B) > 0), + 409 => collection.AsQueryable().Select(x => x.ICSA.CompareTo(x.B) < 0), + 410 => collection.AsQueryable().Select(x => x.ICSA.CompareTo(x.B) < 1), + 411 => collection.AsQueryable().Select(x => x.ICSA.CompareTo(x.B) <= 0), + 412 => collection.AsQueryable().Select(x => x.ICSA.CompareTo(x.B) >= 0), + _ => throw new ArgumentException($"Invalid scenario: {scenario}.") }; @@ -274,7 +496,6 @@ public void Select_String_Compare_field_to_field_with_ignoreCase_should_work(int var queryable = scenario switch { - // Compare field to constant 1 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B, ignoreCase) == -1), 2 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B, ignoreCase) == 0), 3 => collection.AsQueryable().Select(x => string.Compare(x.A, x.B, ignoreCase) == 1), @@ -294,37 +515,112 @@ public void Select_String_Compare_field_to_field_with_ignoreCase_should_work(int } [Theory] - [InlineData(1, "{ $match : { B : { $gt : 'A' } } }", new int[] { 2, 4, 5, 6 })] - [InlineData(2, "{ $match : { B : 'A' } }", new int[] { 1, 3 })] - [InlineData(3, "{ $match : { B : { $lt : 'A' } } }", new int[] { })] - [InlineData(4, "{ $match : { B : { $lte : 'A' } } }", new int[] { 1, 3 })] - [InlineData(5, "{ $match : { B : { $ne : 'A' } } }", new int[] { 2, 4, 5, 6 })] - [InlineData(6, "{ $match : { B : { $gte : 'A' } } }", new int[] { 1, 2, 3, 4, 5, 6 })] - [InlineData(7, "{ $match : { B : { $lte : 'A' } } }", new int[] { 1, 3 })] - [InlineData(8, "{ $match : { B : { $lt : 'A' } } }", new int[] { })] - [InlineData(9, "{ $match : { B : { $gt : 'A' } } }", new int[] { 2, 4, 5, 6 })] - [InlineData(10, "{ $match : { B : { $gte : 'A' } } }", new int[] { 1, 2, 3, 4, 5, 6 })] - [InlineData(11, "{ $match : { B : { $gte : 'A' } } }", new int[] { 1, 2, 3, 4, 5, 6 })] - [InlineData(12, "{ $match : { B : { $lte : 'A' } } }", new int[] { 1, 3 })] + [InlineData(101, "{ $match : { B : { $gt : 'A' } } }", new int[] { 2, 4, 5, 6 })] + [InlineData(102, "{ $match : { B : 'A' } }", new int[] { 1, 3 })] + [InlineData(103, "{ $match : { B : { $lt : 'A' } } }", new int[] { })] + [InlineData(104, "{ $match : { B : { $lte : 'A' } } }", new int[] { 1, 3 })] + [InlineData(105, "{ $match : { B : { $ne : 'A' } } }", new int[] { 2, 4, 5, 6 })] + [InlineData(106, "{ $match : { B : { $gte : 'A' } } }", new int[] { 1, 2, 3, 4, 5, 6 })] + [InlineData(107, "{ $match : { B : { $lte : 'A' } } }", new int[] { 1, 3 })] + [InlineData(108, "{ $match : { B : { $lt : 'A' } } }", new int[] { })] + [InlineData(109, "{ $match : { B : { $gt : 'A' } } }", new int[] { 2, 4, 5, 6 })] + [InlineData(110, "{ $match : { B : { $gte : 'A' } } }", new int[] { 1, 2, 3, 4, 5, 6 })] + [InlineData(111, "{ $match : { B : { $gte : 'A' } } }", new int[] { 1, 2, 3, 4, 5, 6 })] + [InlineData(112, "{ $match : { B : { $lte : 'A' } } }", new int[] { 1, 3 })] + [InlineData(201, "{ $match : { B : { $gt : 'A' } } }", new int[] { 2, 4, 5, 6 })] + [InlineData(202, "{ $match : { B : 'A' } }", new int[] { 1, 3 })] + [InlineData(203, "{ $match : { B : { $lt : 'A' } } }", new int[] { })] + [InlineData(204, "{ $match : { B : { $lte : 'A' } } }", new int[] { 1, 3 })] + [InlineData(205, "{ $match : { B : { $ne : 'A' } } }", new int[] { 2, 4, 5, 6 })] + [InlineData(206, "{ $match : { B : { $gte : 'A' } } }", new int[] { 1, 2, 3, 4, 5, 6 })] + [InlineData(207, "{ $match : { B : { $lte : 'A' } } }", new int[] { 1, 3 })] + [InlineData(208, "{ $match : { B : { $lt : 'A' } } }", new int[] { })] + [InlineData(209, "{ $match : { B : { $gt : 'A' } } }", new int[] { 2, 4, 5, 6 })] + [InlineData(210, "{ $match : { B : { $gte : 'A' } } }", new int[] { 1, 2, 3, 4, 5, 6 })] + [InlineData(211, "{ $match : { B : { $gte : 'A' } } }", new int[] { 1, 2, 3, 4, 5, 6 })] + [InlineData(212, "{ $match : { B : { $lte : 'A' } } }", new int[] { 1, 3 })] + [InlineData(301, "{ $match : { B : { $gt : 'A' } } }", new int[] { 2, 4, 5, 6 })] + [InlineData(302, "{ $match : { B : 'A' } }", new int[] { 1, 3 })] + [InlineData(303, "{ $match : { B : { $lt : 'A' } } }", new int[] { })] + [InlineData(304, "{ $match : { B : { $lte : 'A' } } }", new int[] { 1, 3 })] + [InlineData(305, "{ $match : { B : { $ne : 'A' } } }", new int[] { 2, 4, 5, 6 })] + [InlineData(306, "{ $match : { B : { $gte : 'A' } } }", new int[] { 1, 2, 3, 4, 5, 6 })] + [InlineData(307, "{ $match : { B : { $lte : 'A' } } }", new int[] { 1, 3 })] + [InlineData(308, "{ $match : { B : { $lt : 'A' } } }", new int[] { })] + [InlineData(309, "{ $match : { B : { $gt : 'A' } } }", new int[] { 2, 4, 5, 6 })] + [InlineData(310, "{ $match : { B : { $gte : 'A' } } }", new int[] { 1, 2, 3, 4, 5, 6 })] + [InlineData(311, "{ $match : { B : { $gte : 'A' } } }", new int[] { 1, 2, 3, 4, 5, 6 })] + [InlineData(312, "{ $match : { B : { $lte : 'A' } } }", new int[] { 1, 3 })] + [InlineData(401, "{ $match : { B : { $gt : 'A' } } }", new int[] { 2, 4, 5, 6 })] + [InlineData(402, "{ $match : { B : 'A' } }", new int[] { 1, 3 })] + [InlineData(403, "{ $match : { B : { $lt : 'A' } } }", new int[] { })] + [InlineData(404, "{ $match : { B : { $lte : 'A' } } }", new int[] { 1, 3 })] + [InlineData(405, "{ $match : { B : { $ne : 'A' } } }", new int[] { 2, 4, 5, 6 })] + [InlineData(406, "{ $match : { B : { $gte : 'A' } } }", new int[] { 1, 2, 3, 4, 5, 6 })] + [InlineData(407, "{ $match : { B : { $lte : 'A' } } }", new int[] { 1, 3 })] + [InlineData(408, "{ $match : { B : { $lt : 'A' } } }", new int[] { })] + [InlineData(409, "{ $match : { B : { $gt : 'A' } } }", new int[] { 2, 4, 5, 6 })] + [InlineData(410, "{ $match : { B : { $gte : 'A' } } }", new int[] { 1, 2, 3, 4, 5, 6 })] + [InlineData(411, "{ $match : { B : { $gte : 'A' } } }", new int[] { 1, 2, 3, 4, 5, 6 })] + [InlineData(412, "{ $match : { B : { $lte : 'A' } } }", new int[] { 1, 3 })] public void Where_String_Compare_constant_to_field_should_work(int scenario, string expectedStage, int[] expectedIds) { var collection = Fixture.Collection; var queryable = scenario switch { - // Compare field to constant - 1 => collection.AsQueryable().Where(x => string.Compare("A", x.B) == -1), - 2 => collection.AsQueryable().Where(x => string.Compare("A", x.B) == 0), - 3 => collection.AsQueryable().Where(x => string.Compare("A", x.B) == 1), - 4 => collection.AsQueryable().Where(x => string.Compare("A", x.B) != -1), - 5 => collection.AsQueryable().Where(x => string.Compare("A", x.B) != 0), - 6 => collection.AsQueryable().Where(x => string.Compare("A", x.B) != 1), - 7 => collection.AsQueryable().Where(x => string.Compare("A", x.B) > -1), - 8 => collection.AsQueryable().Where(x => string.Compare("A", x.B) > 0), - 9 => collection.AsQueryable().Where(x => string.Compare("A", x.B) < 0), - 10 => collection.AsQueryable().Where(x => string.Compare("A", x.B) < 1), - 11 => collection.AsQueryable().Where(x => string.Compare("A", x.B) <= 0), - 12 => collection.AsQueryable().Where(x => string.Compare("A", x.B) >= 0), + 101 => collection.AsQueryable().Where(x => String.Compare("A", x.B) == -1), + 102 => collection.AsQueryable().Where(x => String.Compare("A", x.B) == 0), + 103 => collection.AsQueryable().Where(x => String.Compare("A", x.B) == 1), + 104 => collection.AsQueryable().Where(x => String.Compare("A", x.B) != -1), + 105 => collection.AsQueryable().Where(x => String.Compare("A", x.B) != 0), + 106 => collection.AsQueryable().Where(x => String.Compare("A", x.B) != 1), + 107 => collection.AsQueryable().Where(x => String.Compare("A", x.B) > -1), + 108 => collection.AsQueryable().Where(x => String.Compare("A", x.B) > 0), + 109 => collection.AsQueryable().Where(x => String.Compare("A", x.B) < 0), + 110 => collection.AsQueryable().Where(x => String.Compare("A", x.B) < 1), + 111 => collection.AsQueryable().Where(x => String.Compare("A", x.B) <= 0), + 112 => collection.AsQueryable().Where(x => String.Compare("A", x.B) >= 0), + + 201 => collection.AsQueryable().Where(x => "A".CompareTo(x.B) == -1), + 202 => collection.AsQueryable().Where(x => "A".CompareTo(x.B) == 0), + 203 => collection.AsQueryable().Where(x => "A".CompareTo(x.B) == 1), + 204 => collection.AsQueryable().Where(x => "A".CompareTo(x.B) != -1), + 205 => collection.AsQueryable().Where(x => "A".CompareTo(x.B) != 0), + 206 => collection.AsQueryable().Where(x => "A".CompareTo(x.B) != 1), + 207 => collection.AsQueryable().Where(x => "A".CompareTo(x.B) > -1), + 208 => collection.AsQueryable().Where(x => "A".CompareTo(x.B) > 0), + 209 => collection.AsQueryable().Where(x => "A".CompareTo(x.B) < 0), + 210 => collection.AsQueryable().Where(x => "A".CompareTo(x.B) < 1), + 211 => collection.AsQueryable().Where(x => "A".CompareTo(x.B) <= 0), + 212 => collection.AsQueryable().Where(x => "A".CompareTo(x.B) >= 0), + + 301 => collection.AsQueryable().Where(x => ((IComparable)"A").CompareTo(x.B) == -1), + 302 => collection.AsQueryable().Where(x => ((IComparable)"A").CompareTo(x.B) == 0), + 303 => collection.AsQueryable().Where(x => ((IComparable)"A").CompareTo(x.B) == 1), + 304 => collection.AsQueryable().Where(x => ((IComparable)"A").CompareTo(x.B) != -1), + 305 => collection.AsQueryable().Where(x => ((IComparable)"A").CompareTo(x.B) != 0), + 306 => collection.AsQueryable().Where(x => ((IComparable)"A").CompareTo(x.B) != 1), + 307 => collection.AsQueryable().Where(x => ((IComparable)"A").CompareTo(x.B) > -1), + 308 => collection.AsQueryable().Where(x => ((IComparable)"A").CompareTo(x.B) > 0), + 309 => collection.AsQueryable().Where(x => ((IComparable)"A").CompareTo(x.B) < 0), + 310 => collection.AsQueryable().Where(x => ((IComparable)"A").CompareTo(x.B) < 1), + 311 => collection.AsQueryable().Where(x => ((IComparable)"A").CompareTo(x.B) <= 0), + 312 => collection.AsQueryable().Where(x => ((IComparable)"A").CompareTo(x.B) >= 0), + + 401 => collection.AsQueryable().Where(x => ((IComparable)"A").CompareTo(x.B) == -1), + 402 => collection.AsQueryable().Where(x => ((IComparable)"A").CompareTo(x.B) == 0), + 403 => collection.AsQueryable().Where(x => ((IComparable)"A").CompareTo(x.B) == 1), + 404 => collection.AsQueryable().Where(x => ((IComparable)"A").CompareTo(x.B) != -1), + 405 => collection.AsQueryable().Where(x => ((IComparable)"A").CompareTo(x.B) != 0), + 406 => collection.AsQueryable().Where(x => ((IComparable)"A").CompareTo(x.B) != 1), + 407 => collection.AsQueryable().Where(x => ((IComparable)"A").CompareTo(x.B) > -1), + 408 => collection.AsQueryable().Where(x => ((IComparable)"A").CompareTo(x.B) > 0), + 409 => collection.AsQueryable().Where(x => ((IComparable)"A").CompareTo(x.B) < 0), + 410 => collection.AsQueryable().Where(x => ((IComparable)"A").CompareTo(x.B) < 1), + 411 => collection.AsQueryable().Where(x => ((IComparable)"A").CompareTo(x.B) <= 0), + 412 => collection.AsQueryable().Where(x => ((IComparable)"A").CompareTo(x.B) >= 0), + _ => throw new ArgumentException($"Invalid scenario: {scenario}.") }; @@ -362,7 +658,6 @@ public void Where_String_Compare_constant_to_field_with_ignoreCase_should_work(i var queryable = scenario switch { - // Compare field to constant 1 => collection.AsQueryable().Where(x => string.Compare("A", x.B, ignoreCase) == -1), 2 => collection.AsQueryable().Where(x => string.Compare("A", x.B, ignoreCase) == 0), 3 => collection.AsQueryable().Where(x => string.Compare("A", x.B, ignoreCase) == 1), @@ -382,37 +677,112 @@ public void Where_String_Compare_constant_to_field_with_ignoreCase_should_work(i } [Theory] - [InlineData(1, "{ $match : { A : { $lt : 'B' } } }", new int[] { 1, 2 })] - [InlineData(2, "{ $match : { A : 'B' } }", new int[] { 3 })] - [InlineData(3, "{ $match : { A : { $gt : 'B' } } }", new int[] { 4, 5, 6 })] - [InlineData(4, "{ $match : { A : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] - [InlineData(5, "{ $match : { A : { $ne : 'B' } } }", new int[] { 1, 2, 4, 5, 6 })] - [InlineData(6, "{ $match : { A : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] - [InlineData(7, "{ $match : { A : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] - [InlineData(8, "{ $match : { A : { $gt : 'B' } } }", new int[] { 4, 5, 6 })] - [InlineData(9, "{ $match : { A : { $lt : 'B' } } }", new int[] { 1, 2 })] - [InlineData(10, "{ $match : { A : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] - [InlineData(11, "{ $match : { A : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] - [InlineData(12, "{ $match : { A : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] + [InlineData(101, "{ $match : { A : { $lt : 'B' } } }", new int[] { 1, 2 })] + [InlineData(102, "{ $match : { A : 'B' } }", new int[] { 3 })] + [InlineData(103, "{ $match : { A : { $gt : 'B' } } }", new int[] { 4, 5, 6 })] + [InlineData(104, "{ $match : { A : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] + [InlineData(105, "{ $match : { A : { $ne : 'B' } } }", new int[] { 1, 2, 4, 5, 6 })] + [InlineData(106, "{ $match : { A : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] + [InlineData(107, "{ $match : { A : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] + [InlineData(108, "{ $match : { A : { $gt : 'B' } } }", new int[] { 4, 5, 6 })] + [InlineData(109, "{ $match : { A : { $lt : 'B' } } }", new int[] { 1, 2 })] + [InlineData(110, "{ $match : { A : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] + [InlineData(111, "{ $match : { A : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] + [InlineData(112, "{ $match : { A : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] + [InlineData(201, "{ $match : { A : { $lt : 'B' } } }", new int[] { 1, 2 })] + [InlineData(202, "{ $match : { A : 'B' } }", new int[] { 3 })] + [InlineData(203, "{ $match : { A : { $gt : 'B' } } }", new int[] { 4, 5, 6 })] + [InlineData(204, "{ $match : { A : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] + [InlineData(205, "{ $match : { A : { $ne : 'B' } } }", new int[] { 1, 2, 4, 5, 6 })] + [InlineData(206, "{ $match : { A : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] + [InlineData(207, "{ $match : { A : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] + [InlineData(208, "{ $match : { A : { $gt : 'B' } } }", new int[] { 4, 5, 6 })] + [InlineData(209, "{ $match : { A : { $lt : 'B' } } }", new int[] { 1, 2 })] + [InlineData(210, "{ $match : { A : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] + [InlineData(211, "{ $match : { A : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] + [InlineData(212, "{ $match : { A : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] + [InlineData(301, "{ $match : { ICA : { $lt : 'B' } } }", new int[] { 1, 2 })] + [InlineData(302, "{ $match : { ICA : 'B' } }", new int[] { 3 })] + [InlineData(303, "{ $match : { ICA : { $gt : 'B' } } }", new int[] { 4, 5, 6 })] + [InlineData(304, "{ $match : { ICA : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] + [InlineData(305, "{ $match : { ICA : { $ne : 'B' } } }", new int[] { 1, 2, 4, 5, 6 })] + [InlineData(306, "{ $match : { ICA : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] + [InlineData(307, "{ $match : { ICA : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] + [InlineData(308, "{ $match : { ICA : { $gt : 'B' } } }", new int[] { 4, 5, 6 })] + [InlineData(309, "{ $match : { ICA : { $lt : 'B' } } }", new int[] { 1, 2 })] + [InlineData(310, "{ $match : { ICA : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] + [InlineData(311, "{ $match : { ICA : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] + [InlineData(312, "{ $match : { ICA : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] + [InlineData(401, "{ $match : { ICSA : { $lt : 'B' } } }", new int[] { 1, 2 })] + [InlineData(402, "{ $match : { ICSA : 'B' } }", new int[] { 3 })] + [InlineData(403, "{ $match : { ICSA : { $gt : 'B' } } }", new int[] { 4, 5, 6 })] + [InlineData(404, "{ $match : { ICSA : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] + [InlineData(405, "{ $match : { ICSA : { $ne : 'B' } } }", new int[] { 1, 2, 4, 5, 6 })] + [InlineData(406, "{ $match : { ICSA : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] + [InlineData(407, "{ $match : { ICSA : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] + [InlineData(408, "{ $match : { ICSA : { $gt : 'B' } } }", new int[] { 4, 5, 6 })] + [InlineData(409, "{ $match : { ICSA : { $lt : 'B' } } }", new int[] { 1, 2 })] + [InlineData(410, "{ $match : { ICSA : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] + [InlineData(411, "{ $match : { ICSA : { $lte : 'B' } } }", new int[] { 1, 2, 3 })] + [InlineData(412, "{ $match : { ICSA : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })] public void Where_String_Compare_field_to_constant_should_work(int scenario, string expectedStage, int[] expectedIds) { var collection = Fixture.Collection; var queryable = scenario switch { - // Compare field to constant - 1 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") == -1), - 2 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") == 0), - 3 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") == 1), - 4 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") != -1), - 5 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") != 0), - 6 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") != 1), - 7 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") > -1), - 8 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") > 0), - 9 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") < 0), - 10 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") < 1), - 11 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") <= 0), - 12 => collection.AsQueryable().Where(x => string.Compare(x.A, "B") >= 0), + 101 => collection.AsQueryable().Where(x => String.Compare(x.A, "B") == -1), + 102 => collection.AsQueryable().Where(x => String.Compare(x.A, "B") == 0), + 103 => collection.AsQueryable().Where(x => String.Compare(x.A, "B") == 1), + 104 => collection.AsQueryable().Where(x => String.Compare(x.A, "B") != -1), + 105 => collection.AsQueryable().Where(x => String.Compare(x.A, "B") != 0), + 106 => collection.AsQueryable().Where(x => String.Compare(x.A, "B") != 1), + 107 => collection.AsQueryable().Where(x => String.Compare(x.A, "B") > -1), + 108 => collection.AsQueryable().Where(x => String.Compare(x.A, "B") > 0), + 109 => collection.AsQueryable().Where(x => String.Compare(x.A, "B") < 0), + 110 => collection.AsQueryable().Where(x => String.Compare(x.A, "B") < 1), + 111 => collection.AsQueryable().Where(x => String.Compare(x.A, "B") <= 0), + 112 => collection.AsQueryable().Where(x => String.Compare(x.A, "B") >= 0), + + 201 => collection.AsQueryable().Where(x => x.A.CompareTo("B") == -1), + 202 => collection.AsQueryable().Where(x => x.A.CompareTo("B") == 0), + 203 => collection.AsQueryable().Where(x => x.A.CompareTo("B") == 1), + 204 => collection.AsQueryable().Where(x => x.A.CompareTo("B") != -1), + 205 => collection.AsQueryable().Where(x => x.A.CompareTo("B") != 0), + 206 => collection.AsQueryable().Where(x => x.A.CompareTo("B") != 1), + 207 => collection.AsQueryable().Where(x => x.A.CompareTo("B") > -1), + 208 => collection.AsQueryable().Where(x => x.A.CompareTo("B") > 0), + 209 => collection.AsQueryable().Where(x => x.A.CompareTo("B") < 0), + 210 => collection.AsQueryable().Where(x => x.A.CompareTo("B") < 1), + 211 => collection.AsQueryable().Where(x => x.A.CompareTo("B") <= 0), + 212 => collection.AsQueryable().Where(x => x.A.CompareTo("B") >= 0), + + 301 => collection.AsQueryable().Where(x => x.ICA.CompareTo("B") == -1), + 302 => collection.AsQueryable().Where(x => x.ICA.CompareTo("B") == 0), + 303 => collection.AsQueryable().Where(x => x.ICA.CompareTo("B") == 1), + 304 => collection.AsQueryable().Where(x => x.ICA.CompareTo("B") != -1), + 305 => collection.AsQueryable().Where(x => x.ICA.CompareTo("B") != 0), + 306 => collection.AsQueryable().Where(x => x.ICA.CompareTo("B") != 1), + 307 => collection.AsQueryable().Where(x => x.ICA.CompareTo("B") > -1), + 308 => collection.AsQueryable().Where(x => x.ICA.CompareTo("B") > 0), + 309 => collection.AsQueryable().Where(x => x.ICA.CompareTo("B") < 0), + 310 => collection.AsQueryable().Where(x => x.ICA.CompareTo("B") < 1), + 311 => collection.AsQueryable().Where(x => x.ICA.CompareTo("B") <= 0), + 312 => collection.AsQueryable().Where(x => x.ICA.CompareTo("B") >= 0), + + 401 => collection.AsQueryable().Where(x => x.ICSA.CompareTo("B") == -1), + 402 => collection.AsQueryable().Where(x => x.ICSA.CompareTo("B") == 0), + 403 => collection.AsQueryable().Where(x => x.ICSA.CompareTo("B") == 1), + 404 => collection.AsQueryable().Where(x => x.ICSA.CompareTo("B") != -1), + 405 => collection.AsQueryable().Where(x => x.ICSA.CompareTo("B") != 0), + 406 => collection.AsQueryable().Where(x => x.ICSA.CompareTo("B") != 1), + 407 => collection.AsQueryable().Where(x => x.ICSA.CompareTo("B") > -1), + 408 => collection.AsQueryable().Where(x => x.ICSA.CompareTo("B") > 0), + 409 => collection.AsQueryable().Where(x => x.ICSA.CompareTo("B") < 0), + 410 => collection.AsQueryable().Where(x => x.ICSA.CompareTo("B") < 1), + 411 => collection.AsQueryable().Where(x => x.ICSA.CompareTo("B") <= 0), + 412 => collection.AsQueryable().Where(x => x.ICSA.CompareTo("B") >= 0), + _ => throw new ArgumentException($"Invalid scenario: {scenario}.") }; @@ -450,7 +820,6 @@ public void Where_String_Compare_field_to_constant_with_ignoreCase_should_work(i var queryable = scenario switch { - // Compare field to constant 1 => collection.AsQueryable().Where(x => string.Compare(x.A, "B", ignoreCase) == -1), 2 => collection.AsQueryable().Where(x => string.Compare(x.A, "B", ignoreCase) == 0), 3 => collection.AsQueryable().Where(x => string.Compare(x.A, "B", ignoreCase) == 1), @@ -470,37 +839,112 @@ public void Where_String_Compare_field_to_constant_with_ignoreCase_should_work(i } [Theory] - [InlineData(1, "{ $match : { $expr : { $eq : [{ $cmp : ['$A', '$B'] }, -1] } } }", new int[] { 2, 5 })] - [InlineData(2, "{ $match : { $expr : { $eq : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 1, 4 })] - [InlineData(3, "{ $match : { $expr : { $eq : [{ $cmp : ['$A', '$B'] }, 1] } } }", new int[] { 3, 6 })] - [InlineData(4, "{ $match : { $expr : { $ne : [{ $cmp : ['$A', '$B'] }, -1] } } }", new int[] { 1, 3, 4, 6 })] - [InlineData(5, "{ $match : { $expr : { $ne : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 2, 3, 5, 6 })] - [InlineData(6, "{ $match : { $expr : { $ne : [{ $cmp : ['$A', '$B'] }, 1] } } }", new int[] { 1, 2, 4, 5 })] - [InlineData(7, "{ $match : { $expr : { $gt : [{ $cmp : ['$A', '$B'] }, -1] } } }", new int[] { 1, 3, 4, 6 })] - [InlineData(8, "{ $match : { $expr : { $gt : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 3, 6 })] - [InlineData(9, "{ $match : { $expr : { $lt : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 2, 5 })] - [InlineData(10, "{ $match : { $expr : { $lt : [{ $cmp : ['$A', '$B'] }, 1] } } }", new int[] { 1, 2, 4, 5 })] - [InlineData(11, "{ $match : { $expr : { $lte : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 1, 2, 4, 5 })] - [InlineData(12, "{ $match : { $expr : { $gte : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 1, 3, 4, 6 })] + [InlineData(101, "{ $match : { $expr : { $eq : [{ $cmp : ['$A', '$B'] }, -1] } } }", new int[] { 2, 5 })] + [InlineData(102, "{ $match : { $expr : { $eq : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 1, 4 })] + [InlineData(103, "{ $match : { $expr : { $eq : [{ $cmp : ['$A', '$B'] }, 1] } } }", new int[] { 3, 6 })] + [InlineData(104, "{ $match : { $expr : { $ne : [{ $cmp : ['$A', '$B'] }, -1] } } }", new int[] { 1, 3, 4, 6 })] + [InlineData(105, "{ $match : { $expr : { $ne : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 2, 3, 5, 6 })] + [InlineData(106, "{ $match : { $expr : { $ne : [{ $cmp : ['$A', '$B'] }, 1] } } }", new int[] { 1, 2, 4, 5 })] + [InlineData(107, "{ $match : { $expr : { $gt : [{ $cmp : ['$A', '$B'] }, -1] } } }", new int[] { 1, 3, 4, 6 })] + [InlineData(108, "{ $match : { $expr : { $gt : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 3, 6 })] + [InlineData(109, "{ $match : { $expr : { $lt : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 2, 5 })] + [InlineData(110, "{ $match : { $expr : { $lt : [{ $cmp : ['$A', '$B'] }, 1] } } }", new int[] { 1, 2, 4, 5 })] + [InlineData(111, "{ $match : { $expr : { $lte : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 1, 2, 4, 5 })] + [InlineData(112, "{ $match : { $expr : { $gte : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 1, 3, 4, 6 })] + [InlineData(201, "{ $match : { $expr : { $eq : [{ $cmp : ['$A', '$B'] }, -1] } } }", new int[] { 2, 5 })] + [InlineData(202, "{ $match : { $expr : { $eq : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 1, 4 })] + [InlineData(203, "{ $match : { $expr : { $eq : [{ $cmp : ['$A', '$B'] }, 1] } } }", new int[] { 3, 6 })] + [InlineData(204, "{ $match : { $expr : { $ne : [{ $cmp : ['$A', '$B'] }, -1] } } }", new int[] { 1, 3, 4, 6 })] + [InlineData(205, "{ $match : { $expr : { $ne : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 2, 3, 5, 6 })] + [InlineData(206, "{ $match : { $expr : { $ne : [{ $cmp : ['$A', '$B'] }, 1] } } }", new int[] { 1, 2, 4, 5 })] + [InlineData(207, "{ $match : { $expr : { $gt : [{ $cmp : ['$A', '$B'] }, -1] } } }", new int[] { 1, 3, 4, 6 })] + [InlineData(208, "{ $match : { $expr : { $gt : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 3, 6 })] + [InlineData(209, "{ $match : { $expr : { $lt : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 2, 5 })] + [InlineData(210, "{ $match : { $expr : { $lt : [{ $cmp : ['$A', '$B'] }, 1] } } }", new int[] { 1, 2, 4, 5 })] + [InlineData(211, "{ $match : { $expr : { $lte : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 1, 2, 4, 5 })] + [InlineData(212, "{ $match : { $expr : { $gte : [{ $cmp : ['$A', '$B'] }, 0] } } }", new int[] { 1, 3, 4, 6 })] + [InlineData(301, "{ $match : { $expr : { $eq : [{ $cmp : ['$ICA', '$B'] }, -1] } } }", new int[] { 2, 5 })] + [InlineData(302, "{ $match : { $expr : { $eq : [{ $cmp : ['$ICA', '$B'] }, 0] } } }", new int[] { 1, 4 })] + [InlineData(303, "{ $match : { $expr : { $eq : [{ $cmp : ['$ICA', '$B'] }, 1] } } }", new int[] { 3, 6 })] + [InlineData(304, "{ $match : { $expr : { $ne : [{ $cmp : ['$ICA', '$B'] }, -1] } } }", new int[] { 1, 3, 4, 6 })] + [InlineData(305, "{ $match : { $expr : { $ne : [{ $cmp : ['$ICA', '$B'] }, 0] } } }", new int[] { 2, 3, 5, 6 })] + [InlineData(306, "{ $match : { $expr : { $ne : [{ $cmp : ['$ICA', '$B'] }, 1] } } }", new int[] { 1, 2, 4, 5 })] + [InlineData(307, "{ $match : { $expr : { $gt : [{ $cmp : ['$ICA', '$B'] }, -1] } } }", new int[] { 1, 3, 4, 6 })] + [InlineData(308, "{ $match : { $expr : { $gt : [{ $cmp : ['$ICA', '$B'] }, 0] } } }", new int[] { 3, 6 })] + [InlineData(309, "{ $match : { $expr : { $lt : [{ $cmp : ['$ICA', '$B'] }, 0] } } }", new int[] { 2, 5 })] + [InlineData(310, "{ $match : { $expr : { $lt : [{ $cmp : ['$ICA', '$B'] }, 1] } } }", new int[] { 1, 2, 4, 5 })] + [InlineData(311, "{ $match : { $expr : { $lte : [{ $cmp : ['$ICA', '$B'] }, 0] } } }", new int[] { 1, 2, 4, 5 })] + [InlineData(312, "{ $match : { $expr : { $gte : [{ $cmp : ['$ICA', '$B'] }, 0] } } }", new int[] { 1, 3, 4, 6 })] + [InlineData(401, "{ $match : { $expr : { $eq : [{ $cmp : ['$ICSA', '$B'] }, -1] } } }", new int[] { 2, 5 })] + [InlineData(402, "{ $match : { $expr : { $eq : [{ $cmp : ['$ICSA', '$B'] }, 0] } } }", new int[] { 1, 4 })] + [InlineData(403, "{ $match : { $expr : { $eq : [{ $cmp : ['$ICSA', '$B'] }, 1] } } }", new int[] { 3, 6 })] + [InlineData(404, "{ $match : { $expr : { $ne : [{ $cmp : ['$ICSA', '$B'] }, -1] } } }", new int[] { 1, 3, 4, 6 })] + [InlineData(405, "{ $match : { $expr : { $ne : [{ $cmp : ['$ICSA', '$B'] }, 0] } } }", new int[] { 2, 3, 5, 6 })] + [InlineData(406, "{ $match : { $expr : { $ne : [{ $cmp : ['$ICSA', '$B'] }, 1] } } }", new int[] { 1, 2, 4, 5 })] + [InlineData(407, "{ $match : { $expr : { $gt : [{ $cmp : ['$ICSA', '$B'] }, -1] } } }", new int[] { 1, 3, 4, 6 })] + [InlineData(408, "{ $match : { $expr : { $gt : [{ $cmp : ['$ICSA', '$B'] }, 0] } } }", new int[] { 3, 6 })] + [InlineData(409, "{ $match : { $expr : { $lt : [{ $cmp : ['$ICSA', '$B'] }, 0] } } }", new int[] { 2, 5 })] + [InlineData(410, "{ $match : { $expr : { $lt : [{ $cmp : ['$ICSA', '$B'] }, 1] } } }", new int[] { 1, 2, 4, 5 })] + [InlineData(411, "{ $match : { $expr : { $lte : [{ $cmp : ['$ICSA', '$B'] }, 0] } } }", new int[] { 1, 2, 4, 5 })] + [InlineData(412, "{ $match : { $expr : { $gte : [{ $cmp : ['$ICSA', '$B'] }, 0] } } }", new int[] { 1, 3, 4, 6 })] public void Where_String_Compare_field_to_field_should_work(int scenario, string expectedStage, int[] expectedIds) { var collection = Fixture.Collection; var queryable = scenario switch { - // Compare field to constant - 1 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B) == -1), - 2 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B) == 0), - 3 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B) == 1), - 4 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B) != -1), - 5 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B) != 0), - 6 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B) != 1), - 7 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B) > -1), - 8 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B) > 0), - 9 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B) < 0), - 10 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B) < 1), - 11 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B) <= 0), - 12 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B) >= 0), + 101 => collection.AsQueryable().Where(x => String.Compare(x.A, x.B) == -1), + 102 => collection.AsQueryable().Where(x => String.Compare(x.A, x.B) == 0), + 103 => collection.AsQueryable().Where(x => String.Compare(x.A, x.B) == 1), + 104 => collection.AsQueryable().Where(x => String.Compare(x.A, x.B) != -1), + 105 => collection.AsQueryable().Where(x => String.Compare(x.A, x.B) != 0), + 106 => collection.AsQueryable().Where(x => String.Compare(x.A, x.B) != 1), + 107 => collection.AsQueryable().Where(x => String.Compare(x.A, x.B) > -1), + 108 => collection.AsQueryable().Where(x => String.Compare(x.A, x.B) > 0), + 109 => collection.AsQueryable().Where(x => String.Compare(x.A, x.B) < 0), + 110 => collection.AsQueryable().Where(x => String.Compare(x.A, x.B) < 1), + 111 => collection.AsQueryable().Where(x => String.Compare(x.A, x.B) <= 0), + 112 => collection.AsQueryable().Where(x => String.Compare(x.A, x.B) >= 0), + + 201 => collection.AsQueryable().Where(x => x.A.CompareTo(x.B) == -1), + 202 => collection.AsQueryable().Where(x => x.A.CompareTo(x.B) == 0), + 203 => collection.AsQueryable().Where(x => x.A.CompareTo(x.B) == 1), + 204 => collection.AsQueryable().Where(x => x.A.CompareTo(x.B) != -1), + 205 => collection.AsQueryable().Where(x => x.A.CompareTo(x.B) != 0), + 206 => collection.AsQueryable().Where(x => x.A.CompareTo(x.B) != 1), + 207 => collection.AsQueryable().Where(x => x.A.CompareTo(x.B) > -1), + 208 => collection.AsQueryable().Where(x => x.A.CompareTo(x.B) > 0), + 209 => collection.AsQueryable().Where(x => x.A.CompareTo(x.B) < 0), + 210 => collection.AsQueryable().Where(x => x.A.CompareTo(x.B) < 1), + 211 => collection.AsQueryable().Where(x => x.A.CompareTo(x.B) <= 0), + 212 => collection.AsQueryable().Where(x => x.A.CompareTo(x.B) >= 0), + + 301 => collection.AsQueryable().Where(x => x.ICA.CompareTo(x.B) == -1), + 302 => collection.AsQueryable().Where(x => x.ICA.CompareTo(x.B) == 0), + 303 => collection.AsQueryable().Where(x => x.ICA.CompareTo(x.B) == 1), + 304 => collection.AsQueryable().Where(x => x.ICA.CompareTo(x.B) != -1), + 305 => collection.AsQueryable().Where(x => x.ICA.CompareTo(x.B) != 0), + 306 => collection.AsQueryable().Where(x => x.ICA.CompareTo(x.B) != 1), + 307 => collection.AsQueryable().Where(x => x.ICA.CompareTo(x.B) > -1), + 308 => collection.AsQueryable().Where(x => x.ICA.CompareTo(x.B) > 0), + 309 => collection.AsQueryable().Where(x => x.ICA.CompareTo(x.B) < 0), + 310 => collection.AsQueryable().Where(x => x.ICA.CompareTo(x.B) < 1), + 311 => collection.AsQueryable().Where(x => x.ICA.CompareTo(x.B) <= 0), + 312 => collection.AsQueryable().Where(x => x.ICA.CompareTo(x.B) >= 0), + + 401 => collection.AsQueryable().Where(x => x.ICSA.CompareTo(x.B) == -1), + 402 => collection.AsQueryable().Where(x => x.ICSA.CompareTo(x.B) == 0), + 403 => collection.AsQueryable().Where(x => x.ICSA.CompareTo(x.B) == 1), + 404 => collection.AsQueryable().Where(x => x.ICSA.CompareTo(x.B) != -1), + 405 => collection.AsQueryable().Where(x => x.ICSA.CompareTo(x.B) != 0), + 406 => collection.AsQueryable().Where(x => x.ICSA.CompareTo(x.B) != 1), + 407 => collection.AsQueryable().Where(x => x.ICSA.CompareTo(x.B) > -1), + 408 => collection.AsQueryable().Where(x => x.ICSA.CompareTo(x.B) > 0), + 409 => collection.AsQueryable().Where(x => x.ICSA.CompareTo(x.B) < 0), + 410 => collection.AsQueryable().Where(x => x.ICSA.CompareTo(x.B) < 1), + 411 => collection.AsQueryable().Where(x => x.ICSA.CompareTo(x.B) <= 0), + 412 => collection.AsQueryable().Where(x => x.ICSA.CompareTo(x.B) >= 0), + _ => throw new ArgumentException($"Invalid scenario: {scenario}.") }; @@ -538,7 +982,6 @@ public void Where_String_Compare_field_to_field_with_ignoreCase_should_work(int var queryable = scenario switch { - // Compare field to constant 1 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B, ignoreCase) == -1), 2 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B, ignoreCase) == 0), 3 => collection.AsQueryable().Where(x => string.Compare(x.A, x.B, ignoreCase) == 1), @@ -580,18 +1023,20 @@ public class C public int Id { get; set; } public string A { get; set; } public string B { get; set; } + public IComparable ICA { get; set; } + public IComparable ICSA { get; set; } } public sealed class ClassFixture : MongoCollectionFixture { protected override IEnumerable InitialData => [ - new C { Id = 1, A = "A", B = "A" }, - new C { Id = 2, A = "A", B = "B" }, - new C { Id = 3, A = "B", B = "A" }, - new C { Id = 4, A = "a", B = "a" }, - new C { Id = 5, A = "a", B = "b" }, - new C { Id = 6, A = "b", B = "a" } + new C { Id = 1, A = "A", B = "A", ICA = "A", ICSA = "A" }, + new C { Id = 2, A = "A", B = "B", ICA = "A", ICSA = "A" }, + new C { Id = 3, A = "B", B = "A", ICA = "B", ICSA = "B" }, + new C { Id = 4, A = "a", B = "a", ICA = "a", ICSA = "a" }, + new C { Id = 5, A = "a", B = "b", ICA = "a", ICSA = "a" }, + new C { Id = 6, A = "b", B = "a", ICA = "b", ICSA = "b" } ]; } } From 0d10969d2c650a4a31bfa051b57093718dd9e81e Mon Sep 17 00:00:00 2001 From: rstam Date: Thu, 23 Oct 2025 10:46:48 -0400 Subject: [PATCH 9/9] CSHARP-5730: Comitting a file that somehow got left out of the previous commit. --- .../Misc/MethodInfoExtensions.cs | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/MethodInfoExtensions.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/MethodInfoExtensions.cs index 7059f574cea..21e874366cd 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/MethodInfoExtensions.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/MethodInfoExtensions.cs @@ -13,6 +13,8 @@ * limitations under the License. */ +using System; +using System.Linq; using System.Reflection; namespace MongoDB.Driver.Linq.Linq3Implementation.Misc @@ -40,15 +42,26 @@ public static bool Is(this MethodInfo method, MethodInfo comparand) public static bool IsInstanceCompareToMethod(this MethodInfo method) { - return - method.IsPublic && + if (method.IsPublic && !method.IsStatic && method.ReturnType == typeof(int) && method.Name == "CompareTo" && method.GetParameters() is var parameters && - parameters.Length == 1 && - parameters[0].ParameterType is var parameterType && - (parameterType == method.DeclaringType || parameterType == typeof(object)); + parameters.Length == 1) + { + var declaringType = method.DeclaringType; + var comparandType = declaringType switch + { + _ when declaringType == typeof(IComparable) => typeof(object), + _ when declaringType.IsConstructedGenericType && declaringType.GetGenericTypeDefinition() == typeof(IComparable<>) => declaringType.GetGenericArguments().Single(), + _ => declaringType + }; + + var parameterType = parameters[0].ParameterType; + return parameterType == comparandType; + } + + return false; } public static bool IsOneOf(this MethodInfo method, MethodInfo comparand1, MethodInfo comparand2)