Skip to content

Commit f4e62c6

Browse files
committed
CSHARP-5730: Add support for swapping left and right sides of comparision.
1 parent 3a85339 commit f4e62c6

File tree

5 files changed

+180
-122
lines changed

5 files changed

+180
-122
lines changed

src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Filters/AstComparisonFilterOperator.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,20 @@ internal enum AstComparisonFilterOperator
2929

3030
internal static class AstComparisonFilterOperatorExtensions
3131
{
32+
public static AstComparisonFilterOperator GetComparisonOperatorForSwappedLeftAndRight(this AstComparisonFilterOperator @operator)
33+
{
34+
return @operator switch
35+
{
36+
AstComparisonFilterOperator.Eq => AstComparisonFilterOperator.Eq,
37+
AstComparisonFilterOperator.Gt => AstComparisonFilterOperator.Lt,
38+
AstComparisonFilterOperator.Gte => AstComparisonFilterOperator.Lte,
39+
AstComparisonFilterOperator.Lt => AstComparisonFilterOperator.Gt,
40+
AstComparisonFilterOperator.Lte => AstComparisonFilterOperator.Gte,
41+
AstComparisonFilterOperator.Ne => AstComparisonFilterOperator.Ne,
42+
_ => throw new InvalidOperationException($"Unexpected comparison filter operator: {@operator}.")
43+
};
44+
}
45+
3246
public static string Render(this AstComparisonFilterOperator @operator)
3347
{
3448
return @operator switch
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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.Linq.Expressions;
17+
using System.Reflection;
18+
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Filters;
19+
using MongoDB.Driver.Linq.Linq3Implementation.ExtensionMethods;
20+
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
21+
using MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToFilterTranslators.ToFilterFieldTranslators;
22+
23+
namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToFilterTranslators.ExpressionTranslators
24+
{
25+
internal static class CompareComparisonExpressionToFilterTranslator
26+
{
27+
public static bool CanTranslate(Expression leftExpression)
28+
{
29+
return
30+
leftExpression is MethodCallExpression leftMethodCallExpression &&
31+
leftMethodCallExpression.Method is var method &&
32+
(IsStaticCompareMethod(method) || IsInstanceCompareToMethod(method));
33+
}
34+
35+
// caller is responsible for ensuring constant is on the right
36+
public static AstFilter Translate(
37+
TranslationContext context,
38+
Expression expression,
39+
Expression leftExpression,
40+
AstComparisonFilterOperator outerComparisonOperator,
41+
Expression rightExpression)
42+
{
43+
if (CanTranslate(leftExpression))
44+
{
45+
var compareMethodCallExpression = (MethodCallExpression)leftExpression;
46+
var compareMethod = compareMethodCallExpression.Method;
47+
var compareArguments = compareMethodCallExpression.Arguments;
48+
var outerValue = rightExpression.GetConstantValue<int>(containingExpression: expression);
49+
50+
Expression fieldExpression;
51+
Expression innerValueExpression;
52+
if (compareMethod.IsStatic)
53+
{
54+
fieldExpression = compareArguments[0];
55+
innerValueExpression = compareArguments[1];
56+
}
57+
else
58+
{
59+
fieldExpression = compareMethodCallExpression.Object;
60+
innerValueExpression = compareArguments[0];
61+
}
62+
63+
var fieldComparisonOperator = (outerComparisonOperator, outerValue) switch
64+
{
65+
(AstComparisonFilterOperator.Eq, -1) => AstComparisonFilterOperator.Lt,
66+
(AstComparisonFilterOperator.Ne, -1) => AstComparisonFilterOperator.Gte,
67+
(AstComparisonFilterOperator.Gt, -1) => AstComparisonFilterOperator.Gte,
68+
(AstComparisonFilterOperator.Eq, 0) => AstComparisonFilterOperator.Eq,
69+
(AstComparisonFilterOperator.Ne, 0) => AstComparisonFilterOperator.Ne,
70+
(AstComparisonFilterOperator.Lt, 0) => AstComparisonFilterOperator.Lt,
71+
(AstComparisonFilterOperator.Lte, 0) => AstComparisonFilterOperator.Lte,
72+
(AstComparisonFilterOperator.Gt, 0) => AstComparisonFilterOperator.Gt,
73+
(AstComparisonFilterOperator.Gte, 0) => AstComparisonFilterOperator.Gte,
74+
(AstComparisonFilterOperator.Eq, 1) => AstComparisonFilterOperator.Gt,
75+
(AstComparisonFilterOperator.Ne, 1) => AstComparisonFilterOperator.Lte,
76+
(AstComparisonFilterOperator.Lt, 1) => AstComparisonFilterOperator.Lte,
77+
_ => throw new ExpressionNotSupportedException(expression)
78+
};
79+
80+
if (fieldExpression.NodeType == ExpressionType.Constant && innerValueExpression.NodeType != ExpressionType.Constant)
81+
{
82+
(fieldExpression, innerValueExpression) = (innerValueExpression, fieldExpression);
83+
fieldComparisonOperator = fieldComparisonOperator.GetComparisonOperatorForSwappedLeftAndRight();
84+
}
85+
86+
var fieldTranslation = ExpressionToFilterFieldTranslator.Translate(context, fieldExpression);
87+
var value = innerValueExpression.GetConstantValue<object>(containingExpression: expression);
88+
var serializedValue = SerializationHelper.SerializeValue(fieldTranslation.Serializer, value);
89+
90+
return AstFilter.Compare(fieldTranslation.Ast, fieldComparisonOperator, serializedValue);
91+
}
92+
93+
throw new ExpressionNotSupportedException(expression);
94+
}
95+
96+
private static bool IsInstanceCompareToMethod(MethodInfo method)
97+
{
98+
return
99+
method.IsPublic &&
100+
!method.IsStatic &&
101+
method.ReturnType == typeof(int) &&
102+
method.Name == "CompareTo" &&
103+
method.GetParameters() is var parameters &&
104+
parameters.Length == 1 &&
105+
parameters[0].ParameterType is var parameterType &&
106+
(parameterType == method.DeclaringType || parameterType == typeof(object));
107+
}
108+
109+
private static bool IsStaticCompareMethod(MethodInfo method)
110+
{
111+
return
112+
method.IsPublic &&
113+
method.IsStatic &&
114+
method.ReturnType == typeof(int) &&
115+
method.Name == "Compare" &&
116+
method.GetParameters() is var parameters &&
117+
parameters.Length == 2 &&
118+
parameters[0].ParameterType == method.DeclaringType &&
119+
parameters[1].ParameterType == method.DeclaringType;
120+
}
121+
}
122+
}

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

Lines changed: 0 additions & 102 deletions
This file was deleted.

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

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ internal static class ComparisonExpressionToFilterTranslator
2626
{
2727
public static AstFilter Translate(TranslationContext context, BinaryExpression expression)
2828
{
29-
var comparisonOperator = GetComparisonOperator(expression);
29+
var comparisonOperator = GetAstComparisonOperator(expression);
3030
var leftExpression = expression.Left;
3131
var rightExpression = expression.Right;
3232

3333
if (leftExpression.NodeType == ExpressionType.Constant && rightExpression.NodeType != ExpressionType.Constant)
3434
{
35-
comparisonOperator = GetComparisonOperatorForSwappedLeftAndRight(expression);
35+
comparisonOperator = comparisonOperator.GetComparisonOperatorForSwappedLeftAndRight();
3636
(leftExpression, rightExpression) = (rightExpression, leftExpression);
3737
}
3838

@@ -46,9 +46,9 @@ public static AstFilter Translate(TranslationContext context, BinaryExpression e
4646
return BitMaskComparisonExpressionToFilterTranslator.Translate(context, expression, leftExpression, comparisonOperator, rightExpression);
4747
}
4848

49-
if (CompareToComparisonExpressionToFilterTranslator.CanTranslate(leftExpression))
49+
if (CompareComparisonExpressionToFilterTranslator.CanTranslate(leftExpression))
5050
{
51-
return CompareToComparisonExpressionToFilterTranslator.Translate(context, expression, leftExpression, comparisonOperator, rightExpression);
51+
return CompareComparisonExpressionToFilterTranslator.Translate(context, expression, leftExpression, comparisonOperator, rightExpression);
5252
}
5353

5454
if (CountComparisonExpressionToFilterTranslator.CanTranslate(leftExpression, rightExpression, out var countExpression, out sizeExpression))
@@ -129,7 +129,7 @@ fieldOperationFilter.Operation is AstComparisonFilterOperation comparisonOperati
129129
}
130130
}
131131

132-
private static AstComparisonFilterOperator GetComparisonOperator(Expression expression)
132+
private static AstComparisonFilterOperator GetAstComparisonOperator(Expression expression)
133133
{
134134
switch (expression.NodeType)
135135
{
@@ -142,19 +142,5 @@ private static AstComparisonFilterOperator GetComparisonOperator(Expression expr
142142
default: throw new ExpressionNotSupportedException(expression);
143143
}
144144
}
145-
146-
private static AstComparisonFilterOperator GetComparisonOperatorForSwappedLeftAndRight(Expression expression)
147-
{
148-
switch (expression.NodeType)
149-
{
150-
case ExpressionType.Equal: return AstComparisonFilterOperator.Eq;
151-
case ExpressionType.GreaterThan: return AstComparisonFilterOperator.Lt;
152-
case ExpressionType.GreaterThanOrEqual: return AstComparisonFilterOperator.Lte;
153-
case ExpressionType.LessThan: return AstComparisonFilterOperator.Gt;
154-
case ExpressionType.LessThanOrEqual: return AstComparisonFilterOperator.Gte;
155-
case ExpressionType.NotEqual: return AstComparisonFilterOperator.Ne;
156-
default: throw new ExpressionNotSupportedException(expression);
157-
}
158-
}
159145
}
160146
}

tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp5730Tests.cs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public CSharp5730Tests(ClassFixture fixture)
4242
[InlineData( 9, "{ $match : { A : { $lt : 'B' } } }", new int[] { 1, 2 })]
4343
[InlineData(10, "{ $match : { A : { $lte : 'B' } } }", new int[] { 1, 2, 3 })]
4444
[InlineData(11, "{ $match : { A : { $lte : 'B' } } }", new int[] { 1, 2, 3 })]
45-
[InlineData(12, "{ $match : { A : { $gte : 'B' } } }", new int[] { 1, 3, 4, 5, 6 })]
45+
[InlineData(12, "{ $match : { A : { $gte : 'B' } } }", new int[] { 3, 4, 5, 6 })]
4646
public void Where_String_Compare_field_to_constant_should_work(int scenario, string expectedStage, int[] expectedResults)
4747
{
4848
var collection = Fixture.Collection;
@@ -68,6 +68,44 @@ public void Where_String_Compare_field_to_constant_should_work(int scenario, str
6868
Assert(collection, queryable, expectedStage, expectedResults);
6969
}
7070

71+
[Theory]
72+
[InlineData( 1, "{ $match : { B : { $gt : 'A' } } }", new int[] { 2, 4, 5, 6 })]
73+
[InlineData( 2, "{ $match : { B : 'A' } }", new int[] { 1, 3 })]
74+
[InlineData( 3, "{ $match : { B : { $lt : 'A' } } }", new int[] { })]
75+
[InlineData( 4, "{ $match : { B : { $lte : 'A' } } }", new int[] { 1, 3 })]
76+
[InlineData( 5, "{ $match : { B : { $ne : 'A' } } }", new int[] { 2, 4, 5, 6 })]
77+
[InlineData( 6, "{ $match : { B : { $gte : 'A' } } }", new int[] { 1, 2, 3, 4, 5, 6 })]
78+
[InlineData( 7, "{ $match : { B : { $lte : 'A' } } }", new int[] { 1, 3 })]
79+
[InlineData( 8, "{ $match : { B : { $lt : 'A' } } }", new int[] { })]
80+
[InlineData( 9, "{ $match : { B : { $gt : 'A' } } }", new int[] { 2, 4, 5, 6 })]
81+
[InlineData(10, "{ $match : { B : { $gte : 'A' } } }", new int[] { 1, 2, 3, 4, 5, 6 })]
82+
[InlineData(11, "{ $match : { B : { $gte : 'A' } } }", new int[] { 1, 2, 3, 4, 5, 6 })]
83+
[InlineData(12, "{ $match : { B : { $lte : 'A' } } }", new int[] { 1, 3 })]
84+
public void Where_String_Compare_constant_to_field_should_work(int scenario, string expectedStage, int[] expectedResults)
85+
{
86+
var collection = Fixture.Collection;
87+
88+
var queryable = scenario switch
89+
{
90+
// Compare field to constant
91+
1 => collection.AsQueryable().Where(x => string.Compare ("A", x.B) == -1),
92+
2 => collection.AsQueryable().Where(x => string.Compare ("A", x.B) == 0),
93+
3 => collection.AsQueryable().Where(x => string.Compare ("A", x.B) == 1),
94+
4 => collection.AsQueryable().Where(x => string.Compare ("A", x.B) != -1),
95+
5 => collection.AsQueryable().Where(x => string.Compare ("A", x.B) != 0),
96+
6 => collection.AsQueryable().Where(x => string.Compare ("A", x.B) != 1),
97+
7 => collection.AsQueryable().Where(x => string.Compare ("A", x.B) > -1),
98+
8 => collection.AsQueryable().Where(x => string.Compare ("A", x.B) > 0),
99+
9 => collection.AsQueryable().Where(x => string.Compare ("A", x.B) < 0),
100+
10 => collection.AsQueryable().Where(x => string.Compare("A", x.B) < 1),
101+
11 => collection.AsQueryable().Where(x => string.Compare("A", x.B) <= 0),
102+
12 => collection.AsQueryable().Where(x => string.Compare("A", x.B) >= 0),
103+
_ => throw new ArgumentException($"Invalid scenario: {scenario}.")
104+
};
105+
106+
Assert(collection, queryable, expectedStage, expectedResults);
107+
}
108+
71109
private void Assert(IMongoCollection<C> collection, IQueryable<C> queryable, string expectedStage, int[] expectedResults)
72110
{
73111
var stages = Translate(collection, queryable);

0 commit comments

Comments
 (0)