Skip to content

Commit 477514d

Browse files
committed
CSHARP-4937: Support CompareTo via the IComparable interface.
1 parent 952c589 commit 477514d

File tree

4 files changed

+213
-28
lines changed

4 files changed

+213
-28
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.Reflection;
18+
19+
namespace MongoDB.Driver.Linq.Linq3Implementation.Reflection
20+
{
21+
internal static class IComparableMethod
22+
{
23+
// public static methods
24+
public static bool IsCompareToMethod(MethodInfo method)
25+
{
26+
var parameters = method.GetParameters();
27+
28+
if (method.Name == "CompareTo" &&
29+
method.IsPublic &&
30+
!method.IsStatic &&
31+
method.ReturnType == typeof(int) &&
32+
parameters.Length == 1)
33+
{
34+
var declaringType = method.DeclaringType;
35+
var parameterType = parameters[0].ParameterType;
36+
37+
if (parameterType == typeof(object) || parameterType == declaringType)
38+
{
39+
return true;
40+
}
41+
42+
if (declaringType.IsConstructedGenericType &&
43+
declaringType.GetGenericTypeDefinition() == typeof(IComparable<>) &&
44+
declaringType.GetGenericArguments()[0] is var valueType &&
45+
parameterType == valueType)
46+
{
47+
return true;
48+
}
49+
}
50+
51+
return false;
52+
}
53+
}
54+
}

src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/CompareToMethodToAggregationExpressionTranslator.cs

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
*/
1515

1616
using System.Linq.Expressions;
17-
using System.Reflection;
1817
using MongoDB.Bson.Serialization.Serializers;
1918
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions;
19+
using MongoDB.Driver.Linq.Linq3Implementation.Reflection;
2020

2121
namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.MethodTranslators
2222
{
@@ -27,7 +27,7 @@ public static AggregationExpression Translate(TranslationContext context, Method
2727
var method = expression.Method;
2828
var arguments = expression.Arguments;
2929

30-
if (IsInstanceCompareToMethod(method))
30+
if (IComparableMethod.IsCompareToMethod(method))
3131
{
3232
var objectExpression = expression.Object;
3333
var objectTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, objectExpression);
@@ -39,16 +39,5 @@ public static AggregationExpression Translate(TranslationContext context, Method
3939

4040
throw new ExpressionNotSupportedException(expression);
4141
}
42-
43-
private static bool IsInstanceCompareToMethod(MethodInfo method)
44-
{
45-
var parameters = method.GetParameters();
46-
return
47-
!method.IsStatic &&
48-
method.ReturnParameter.ParameterType == typeof(int) &&
49-
method.Name == "CompareTo" &&
50-
parameters.Length == 1 &&
51-
parameters[0].ParameterType == method.DeclaringType;
52-
}
5342
}
5443
}

src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ExpressionTranslators/CompareToComparisonExpressionToFilterTranslator.cs

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@
1414
*/
1515

1616
using System.Linq.Expressions;
17-
using System.Reflection;
1817
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Filters;
1918
using MongoDB.Driver.Linq.Linq3Implementation.ExtensionMethods;
2019
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
20+
using MongoDB.Driver.Linq.Linq3Implementation.Reflection;
2121
using MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToFilterTranslators.ToFilterFieldTranslators;
2222

2323
namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToFilterTranslators.ExpressionTranslators
@@ -28,7 +28,7 @@ public static bool CanTranslate(Expression leftExpression)
2828
{
2929
return
3030
leftExpression is MethodCallExpression leftMethodCallExpression &&
31-
IsCompareToMethod(leftMethodCallExpression.Method);
31+
IComparableMethod.IsCompareToMethod(leftMethodCallExpression.Method);
3232
}
3333

3434
// caller is responsible for ensuring constant is on the right
@@ -40,7 +40,7 @@ public static AstFilter Translate(
4040
Expression rightExpression)
4141
{
4242
if (leftExpression is MethodCallExpression leftMethodCallExpression &&
43-
IsCompareToMethod(leftMethodCallExpression.Method))
43+
IComparableMethod.IsCompareToMethod(leftMethodCallExpression.Method))
4444
{
4545
var fieldExpression = leftMethodCallExpression.Object;
4646
var field = ExpressionToFilterFieldTranslator.Translate(context, fieldExpression);
@@ -58,17 +58,5 @@ public static AstFilter Translate(
5858

5959
throw new ExpressionNotSupportedException(expression);
6060
}
61-
62-
private static bool IsCompareToMethod(MethodInfo method)
63-
{
64-
ParameterInfo[] parameters;
65-
return
66-
method.IsPublic == true &&
67-
method.IsStatic == false &&
68-
method.ReturnType == typeof(int) &&
69-
method.Name == "CompareTo" &&
70-
(parameters = method.GetParameters()).Length == 1 &&
71-
(parameters[0].ParameterType == typeof(object) || parameters[0].ParameterType == method.DeclaringType);
72-
}
7361
}
7462
}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.Linq;
18+
using System.Linq.Expressions;
19+
using FluentAssertions;
20+
using MongoDB.Driver.Linq;
21+
using MongoDB.TestHelpers.XunitExtensions;
22+
using Xunit;
23+
24+
namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira
25+
{
26+
public class CSharp4937Tests : Linq3IntegrationTest
27+
{
28+
[Theory]
29+
[InlineData(2, "<" , 0, LinqProvider.V2, "{ _id : { $lt : 2 } }", new int[] { 1 })]
30+
[InlineData(2, "<=", 0, LinqProvider.V2, "{ _id : { $lte : 2 } }", new int[] { 1, 2 })]
31+
[InlineData(2, "==", 0, LinqProvider.V2, "{ _id : 2 }", new int[] { 2 })]
32+
[InlineData(2, "!=", 0, LinqProvider.V2, "{ _id : { $ne : 2 } }", new int[] { 1, 3 })]
33+
[InlineData(2, ">=", 0, LinqProvider.V2, "{ _id : { $gte : 2 } }", new int[] { 2, 3 })]
34+
[InlineData(2, ">" , 0, LinqProvider.V2, "{ _id : { $gt : 2 } }", new int[] { 3 })]
35+
[InlineData(2, "<" , 0, LinqProvider.V3, "{ _id : { $lt : 2 } }", new int[] { 1 })]
36+
[InlineData(2, "<=", 0, LinqProvider.V3, "{ _id : { $lte : 2 } }", new int[] { 1, 2 })]
37+
[InlineData(2, "==", 0, LinqProvider.V3, "{ _id : 2 }", new int[] { 2 })]
38+
[InlineData(2, "!=", 0, LinqProvider.V3, "{ _id : { $ne : 2 } }", new int[] { 1, 3 })]
39+
[InlineData(2, ">=", 0, LinqProvider.V3, "{ _id : { $gte : 2 } }", new int[] { 2, 3 })]
40+
[InlineData(2, ">" , 0, LinqProvider.V3, "{ _id : { $gt : 2 } }", new int[] { 3 })]
41+
public void CompareTo_filter_should_work(
42+
int comparand,
43+
string comparisonOperator,
44+
int compareToResult,
45+
LinqProvider linqProvider,
46+
string expectedFilter,
47+
int[] expectedIds)
48+
{
49+
var collection = GetCollection(linqProvider);
50+
51+
Expression<Func<C, bool>> predicate = comparisonOperator switch
52+
{
53+
"<" => x => x.Id.CompareTo(comparand) < compareToResult,
54+
"<=" => x => x.Id.CompareTo(comparand) <= compareToResult,
55+
"==" => x => x.Id.CompareTo(comparand) == compareToResult,
56+
"!=" => x => x.Id.CompareTo(comparand) != compareToResult,
57+
">=" => x => x.Id.CompareTo(comparand) >= compareToResult,
58+
">" => x => x.Id.CompareTo(comparand) > compareToResult,
59+
_ => throw new InvalidOperationException()
60+
};
61+
62+
Implementation(collection, comparand, predicate);
63+
64+
void Implementation<TModel, TId>(IMongoCollection<TModel> collection, TId _, Expression<Func<TModel, bool>> predicate)
65+
where TModel : IIdentity<TId>
66+
where TId : IComparable<TId>
67+
{
68+
var queryable = collection.AsQueryable()
69+
.Where(predicate);
70+
71+
var stages = Translate(collection, queryable);
72+
var expectedStage = "{ $match : <filter> }".Replace("<filter>", expectedFilter);
73+
AssertStages(stages, expectedStage);
74+
75+
var results = queryable.ToList();
76+
results.Select(x => x.Id).Should().Equal(expectedIds);
77+
}
78+
}
79+
80+
[Theory]
81+
[InlineData(2, "<", 0, LinqProvider.V2, "__fld0 : { $lt : [{ $cmp : ['$_id', 2] }, 0] }", new bool[] { true, false, false })]
82+
[InlineData(2, "<=", 0, LinqProvider.V2, "__fld0 : { $lte : [{ $cmp : ['$_id', 2] }, 0] }", new bool[] { true, true, false })]
83+
[InlineData(2, "==", 0, LinqProvider.V2, "__fld0 : { $eq : [{ $cmp : ['$_id', 2] }, 0] }", new bool[] { false, true, false })]
84+
[InlineData(2, "!=", 0, LinqProvider.V2, "__fld0 : { $ne : [{ $cmp : ['$_id', 2] }, 0] }", new bool[] { true, false, true })]
85+
[InlineData(2, ">=", 0, LinqProvider.V2, "__fld0 : { $gte : [{ $cmp : ['$_id', 2] }, 0] }", new bool[] { false, true, true})]
86+
[InlineData(2, ">", 0, LinqProvider.V2, "__fld0 : { $gt : [{ $cmp : ['$_id', 2] }, 0] }", new bool[] { false, false, true })]
87+
[InlineData(2, "<", 0, LinqProvider.V3, "_v : { $lt : [{ $cmp : ['$_id', 2] }, 0] }", new bool[] { true, false, false })]
88+
[InlineData(2, "<=", 0, LinqProvider.V3, "_v : { $lte : [{ $cmp : ['$_id', 2] }, 0] }", new bool[] { true, true, false })]
89+
[InlineData(2, "==", 0, LinqProvider.V3, "_v : { $eq : [{ $cmp : ['$_id', 2] }, 0] }", new bool[] { false, true, false })]
90+
[InlineData(2, "!=", 0, LinqProvider.V3, "_v : { $ne : [{ $cmp : ['$_id', 2] }, 0] }", new bool[] { true, false, true })]
91+
[InlineData(2, ">=", 0, LinqProvider.V3, "_v : { $gte : [{ $cmp : ['$_id', 2] }, 0] }", new bool[] { false, true, true })]
92+
[InlineData(2, ">", 0, LinqProvider.V3, "_v : { $gt : [{ $cmp : ['$_id', 2] }, 0] }", new bool[] { false, false, true })]
93+
public void CompareTo_expression_should_work(
94+
int comparand,
95+
string comparisonOperator,
96+
int compareToResult,
97+
LinqProvider linqProvider,
98+
string expectedProjection,
99+
bool[] expectedResults)
100+
{
101+
var collection = GetCollection(linqProvider);
102+
103+
Expression<Func<C, bool>> selector = comparisonOperator switch
104+
{
105+
"<" => x => x.Id.CompareTo(comparand) < compareToResult,
106+
"<=" => x => x.Id.CompareTo(comparand) <= compareToResult,
107+
"==" => x => x.Id.CompareTo(comparand) == compareToResult,
108+
"!=" => x => x.Id.CompareTo(comparand) != compareToResult,
109+
">=" => x => x.Id.CompareTo(comparand) >= compareToResult,
110+
">" => x => x.Id.CompareTo(comparand) > compareToResult,
111+
_ => throw new InvalidOperationException()
112+
};
113+
114+
Implementation(collection, comparand, selector);
115+
116+
void Implementation<TModel, TId>(IMongoCollection<TModel> collection, TId _, Expression<Func<TModel, bool>> selector)
117+
where TModel : IIdentity<TId>
118+
where TId : IComparable<TId>
119+
{
120+
var queryable = collection.AsQueryable()
121+
.Select(selector);
122+
123+
var stages = Translate(collection, queryable);
124+
var fieldName = linqProvider == LinqProvider.V2 ? "__fld0" : "_v";
125+
var expectedStage = "{ $project : { <projection>, _id : 0 } }".Replace("<projection>", expectedProjection);
126+
AssertStages(stages, expectedStage);
127+
128+
var results = queryable.ToList();
129+
results.Should().Equal(expectedResults);
130+
}
131+
}
132+
133+
private IMongoCollection<C> GetCollection(LinqProvider linqProvider)
134+
{
135+
var collection = GetCollection<C>("test", linqProvider);
136+
CreateCollection(
137+
collection,
138+
new C { Id = 1 },
139+
new C { Id = 2 },
140+
new C { Id = 3 });
141+
return collection;
142+
}
143+
144+
public interface IIdentity<TId>
145+
{
146+
public TId Id { get; set; }
147+
}
148+
149+
private class C : IIdentity<int>
150+
{
151+
public int Id { get; set; }
152+
}
153+
}
154+
}

0 commit comments

Comments
 (0)