Skip to content

Commit 157e479

Browse files
rstamBorisDog
authored andcommitted
CSHARP-3140: PartialEvaluator should not evaluate unnecessary clauses for AndAlso, Conditional and OrElse.
1 parent a9767f1 commit 157e479

File tree

2 files changed

+248
-0
lines changed

2 files changed

+248
-0
lines changed

src/MongoDB.Driver/Linq/Linq3Implementation/Misc/PartialEvaluator.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,61 @@ public override Expression Visit(Expression expression)
7171
return base.Visit(expression);
7272
}
7373

74+
protected override Expression VisitBinary(BinaryExpression node)
75+
{
76+
if (node.NodeType == ExpressionType.AndAlso)
77+
{
78+
var leftExpression = Visit(node.Left);
79+
if (leftExpression is ConstantExpression constantLeftExpression )
80+
{
81+
var value = (bool)constantLeftExpression.Value;
82+
return value ? Visit(node.Right) : Expression.Constant(false);
83+
}
84+
85+
var rightExpression = Visit(node.Right);
86+
if (rightExpression is ConstantExpression constantRightExpression)
87+
{
88+
var value = (bool)constantRightExpression.Value;
89+
return value ? leftExpression : Expression.Constant(false);
90+
}
91+
92+
return node.Update(leftExpression, conversion: null, rightExpression);
93+
}
94+
95+
if (node.NodeType == ExpressionType.OrElse)
96+
{
97+
var leftExpression = Visit(node.Left);
98+
if (leftExpression is ConstantExpression constantLeftExpression)
99+
{
100+
var value = (bool)constantLeftExpression.Value;
101+
return value ? Expression.Constant(true) : Visit(node.Right);
102+
}
103+
104+
var rightExpression = Visit(node.Right);
105+
if (rightExpression is ConstantExpression constantRightExpression)
106+
{
107+
var value = (bool)constantRightExpression.Value;
108+
return value ? Expression.Constant(true) : leftExpression;
109+
}
110+
111+
return node.Update(leftExpression, conversion: null, rightExpression);
112+
}
113+
114+
return base.VisitBinary(node);
115+
}
116+
117+
protected override Expression VisitConditional(ConditionalExpression node)
118+
{
119+
var test = base.Visit(node.Test);
120+
if (test is ConstantExpression constantTestExpression)
121+
{
122+
var value = (bool)constantTestExpression.Value;
123+
return value ? Visit(node.IfTrue) : Visit(node.IfFalse);
124+
}
125+
126+
return node.Update(test, Visit(node.IfTrue), Visit(node.IfFalse));
127+
}
128+
74129
// private methods
75130
private Expression Evaluate(Expression expression)
76131
{
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
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;
17+
using Xunit;
18+
19+
namespace MongoDB.Driver.Tests.Linq.Linq3ImplementationTests.Jira
20+
{
21+
public class CSharp3140Tests : Linq3IntegrationTest
22+
{
23+
[Fact]
24+
public void AndAlso_with_first_clause_that_evaluates_to_true_should_simplify_to_second_clause()
25+
{
26+
var collection = GetCollection<Order>();
27+
var currentUser = new User { Id = 1, Factory = new Factory { Id = 1 } };
28+
var queryable = collection.AsQueryable()
29+
.Where(x => currentUser.Factory != null && x.FactoryId == currentUser.Factory.Id);
30+
31+
var stages = Translate(collection, queryable);
32+
AssertStages(stages, "{ $match : { FactoryId : 1 } }");
33+
}
34+
35+
[Fact]
36+
public void AndAlso_with_first_clause_that_evaluates_to_false_should_simplify_to_false_and_should_not_evaluate_second_clause()
37+
{
38+
var collection = GetCollection<Order>();
39+
var currentUser = new User { Id = 1, Factory = null };
40+
var queryable = collection.AsQueryable()
41+
.Where(x => currentUser.Factory != null && x.FactoryId == currentUser.Factory.Id);
42+
43+
var stages = Translate(collection, queryable);
44+
AssertStages(stages, "{ $match : { _id : { $type : -1 } } }");
45+
}
46+
47+
[Fact]
48+
public void AndAlso_with_second_clause_that_evaluates_to_true_should_simplify_to_first_cluase()
49+
{
50+
var collection = GetCollection<Order>();
51+
var currentUser = new User { Id = 1, Factory = new Factory { Id = 1 } };
52+
var queryable = collection.AsQueryable()
53+
.Where(x => x.FactoryId == currentUser.Factory.Id && currentUser.Factory != null);
54+
55+
var stages = Translate(collection, queryable);
56+
AssertStages(stages, "{ $match : { FactoryId : 1 } }");
57+
}
58+
59+
[Fact]
60+
public void AndAlso_with_second_clause_that_evaluates_to_false_should_simplify_to_false()
61+
{
62+
var collection = GetCollection<Order>();
63+
var currentUser = new User { Id = 1, Factory = null };
64+
var queryable = collection.AsQueryable()
65+
.Where(x => x.FactoryId != 0 && currentUser.Factory != null);
66+
67+
var stages = Translate(collection, queryable);
68+
AssertStages(stages, "{ $match : { _id : { $type : -1 } } }");
69+
}
70+
71+
[Fact]
72+
public void AndAlso_with_neither_clause_a_constant_should_work()
73+
{
74+
var collection = GetCollection<Order>();
75+
var queryable = collection.AsQueryable()
76+
.Where(x => x.FactoryId != 0 && x.FactoryId != 1);
77+
78+
var stages = Translate(collection, queryable);
79+
AssertStages(stages, "{ $match : { $and : [{ FactoryId : { $ne : 0 } }, { FactoryId : { $ne : 1 } }] } }");
80+
}
81+
82+
[Fact]
83+
public void Conditional_with_test_that_evaluates_to_true_should_simplify_to_if_true_clause()
84+
{
85+
var collection = GetCollection<Order>();
86+
var currentUser = new User { Id = 1, Factory = null };
87+
var queryable = collection.AsQueryable()
88+
.Where(x => x.FactoryId == (currentUser.Factory == null ? 0 : currentUser.Factory.Id));
89+
90+
var stages = Translate(collection, queryable);
91+
AssertStages(stages, "{ $match : { FactoryId : 0 } }");
92+
}
93+
94+
[Fact]
95+
public void Conditional_with_test_that_evaluates_to_false_should_simplify_to_if_true_clause()
96+
{
97+
var collection = GetCollection<Order>();
98+
var currentUser = new User { Id = 1, Factory = new Factory { Id = 1 } };
99+
var queryable = collection.AsQueryable()
100+
.Where(x => x.FactoryId == (currentUser.Factory == null ? 0 : currentUser.Factory.Id));
101+
102+
var stages = Translate(collection, queryable);
103+
AssertStages(stages, "{ $match : { FactoryId : 1 } }");
104+
}
105+
106+
[Fact]
107+
public void Conditional_with_test_that_is_not_a_constant_should_work()
108+
{
109+
var collection = GetCollection<Order>();
110+
var queryable = collection.AsQueryable()
111+
.Where(x => x.FactoryId == (x.Id == 0 ? 0 : 1));
112+
113+
var stages = Translate(collection, queryable);
114+
AssertStages(stages, "{ $match : { $expr : { $eq : ['$FactoryId', { $cond : { if : { $eq : ['$_id', 0] }, then : 0, else : 1 } }] } } }");
115+
}
116+
117+
[Fact]
118+
public void OrElse_with_first_clause_that_evaluates_to_false_should_simplify_to_second_clause()
119+
{
120+
var collection = GetCollection<Order>();
121+
var currentUser = new User { Id = 1, Factory = new Factory { Id = 1 } };
122+
var queryable = collection.AsQueryable()
123+
.Where(x => currentUser.Factory == null || x.FactoryId == currentUser.Factory.Id);
124+
125+
var stages = Translate(collection, queryable);
126+
AssertStages(stages, "{ $match : { FactoryId : 1 } }");
127+
}
128+
129+
[Fact]
130+
public void OrElse_with_first_clause_that_evaluates_to_true_should_simplify_to_true_and_should_not_evaluate_second_clause()
131+
{
132+
var collection = GetCollection<Order>();
133+
var currentUser = new User { Id = 1, Factory = null };
134+
var queryable = collection.AsQueryable()
135+
.Where(x => currentUser.Factory == null || x.FactoryId == currentUser.Factory.Id);
136+
137+
var stages = Translate(collection, queryable);
138+
AssertStages(stages, "{ $match : { } }");
139+
}
140+
141+
[Fact]
142+
public void OrElse_with_second_clause_that_evaluates_to_false_should_simplify_to_first_cluase()
143+
{
144+
var collection = GetCollection<Order>();
145+
var currentUser = new User { Id = 1, Factory = new Factory { Id = 1 } };
146+
var queryable = collection.AsQueryable()
147+
.Where(x => x.FactoryId == currentUser.Factory.Id || currentUser.Factory == null);
148+
149+
var stages = Translate(collection, queryable);
150+
AssertStages(stages, "{ $match : { FactoryId : 1 } }");
151+
}
152+
153+
[Fact]
154+
public void OrElse_with_second_clause_that_evaluates_to_true_should_simplify_to_true()
155+
{
156+
var collection = GetCollection<Order>();
157+
var currentUser = new User { Id = 1, Factory = null };
158+
var queryable = collection.AsQueryable()
159+
.Where(x => x.FactoryId != 0 || currentUser.Factory == null);
160+
161+
var stages = Translate(collection, queryable);
162+
AssertStages(stages, "{ $match : { } }");
163+
}
164+
165+
[Fact]
166+
public void OrElse_with_neither_clause_a_constant_should_work()
167+
{
168+
var collection = GetCollection<Order>();
169+
var queryable = collection.AsQueryable()
170+
.Where(x => x.FactoryId == 0 || x.FactoryId == 1);
171+
172+
var stages = Translate(collection, queryable);
173+
AssertStages(stages, "{ $match : { $or : [{ FactoryId : 0 }, { FactoryId : 1 }] } }");
174+
}
175+
176+
public class Order
177+
{
178+
public int Id { get; set; }
179+
public int FactoryId { get; set; }
180+
}
181+
182+
public class User
183+
{
184+
public int Id { get; set; }
185+
public Factory Factory { get; set; }
186+
}
187+
188+
public class Factory
189+
{
190+
public int Id { get; set; }
191+
}
192+
}
193+
}

0 commit comments

Comments
 (0)