Skip to content

Commit abd1a20

Browse files
committed
Reenable use of SelectClauseVisitor for subqueries
1 parent 20ec240 commit abd1a20

File tree

5 files changed

+89
-65
lines changed

5 files changed

+89
-65
lines changed

src/NHibernate.Test/Async/Linq/WhereTests.cs

+41-28
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,11 @@ public async Task WhereWithConstantExpressionAsync()
7272
public async Task CanUseStringEnumInConditionalAsync()
7373
{
7474
var query = db.Users
75-
.Where(
76-
user => (user.Enum1 == EnumStoredAsString.Small
77-
? EnumStoredAsString.Small
78-
: EnumStoredAsString.Large) == user.Enum1)
79-
.Select(x => x.Enum1);
75+
.Where(
76+
user => (user.Enum1 == EnumStoredAsString.Small
77+
? EnumStoredAsString.Small
78+
: EnumStoredAsString.Large) == user.Enum1)
79+
.Select(x => x.Enum1);
8080

8181
Assert.That(await (query.CountAsync()), Is.GreaterThan(0));
8282
}
@@ -85,11 +85,11 @@ public async Task CanUseStringEnumInConditionalAsync()
8585
public async Task CanUseStringEnumInConditional2Async()
8686
{
8787
var query = db.Users
88-
.Where(
89-
user => (user.Enum1 == EnumStoredAsString.Small
90-
? user.Enum1
91-
: EnumStoredAsString.Large) == user.Enum1)
92-
.Select(x => x.Enum1);
88+
.Where(
89+
user => (user.Enum1 == EnumStoredAsString.Small
90+
? user.Enum1
91+
: EnumStoredAsString.Large) == user.Enum1)
92+
.Select(x => x.Enum1);
9393

9494
Assert.That(await (query.CountAsync()), Is.GreaterThan(0));
9595
}
@@ -329,11 +329,11 @@ public async Task UsersWithEntityPropertiesThreeLevelsDeepAsync()
329329
var query = from user in db.Users
330330
where user.Role.Entity.Output != null
331331
select new
332-
{
333-
user.Name,
334-
RoleName = user.Role.Name,
335-
user.Role.Entity.Output
336-
};
332+
{
333+
user.Name,
334+
RoleName = user.Role.Name,
335+
user.Role.Entity.Output
336+
};
337337

338338
var list = await (query.ToListAsync());
339339
Assert.That(list.Count, Is.EqualTo(1));
@@ -522,7 +522,7 @@ public async Task UsersWithListContains_MutatingListDoesNotBreakOtherSessionsAsy
522522
where names.Contains(user.Name)
523523
select user).ToListAsync());
524524

525-
Assert.AreEqual(2, query.Count);
525+
Assert.AreEqual(2, query.Count);
526526

527527
names.Clear();
528528
}
@@ -679,12 +679,25 @@ public async Task TimesheetsWithEnumerableContainsOnSelectAsync()
679679

680680
var value = (EnumStoredAsInt32) 1000;
681681
var query = await ((from sheet in db.Timesheets
682-
where sheet.Users.Select(x => x.NullableEnum2 ?? value).Contains(value)
683-
select sheet).ToListAsync());
682+
where sheet.Users.Select(x => x.NullableEnum2 ?? value).Contains(value)
683+
select sheet).ToListAsync());
684684

685685
Assert.That(query.Count, Is.EqualTo(1));
686686
}
687687

688+
[Test]
689+
public async Task TimesheetsWithProjectionInSubqueryAsync()
690+
{
691+
if (Dialect is MsSqlCeDialect)
692+
Assert.Ignore("Dialect is not supported");
693+
694+
var query = await ((from sheet in db.Timesheets
695+
where sheet.Users.Select(x => new { Id = x.Id, Name = x.Name }).Any(x => x.Id == 1)
696+
select sheet).ToListAsync());
697+
698+
Assert.That(query.Count, Is.EqualTo(2));
699+
}
700+
688701
[Test]
689702
public async Task ContainsSubqueryWithCoalesceStringEnumSelectAsync()
690703
{
@@ -786,10 +799,10 @@ public async Task BitwiseQuery3Async()
786799
var featureSet = FeatureSet.HasThat;
787800
var query = await ((
788801
from o in session.Query<User>()
789-
// When converted to SQL, "undue" parenthesis are stripped out. For most DB, binary operators have same precedence,
790-
// causing "((o.Features | featureSet) & featureSet)" to be equivalent to "o.Features | featureSet & featureSet"
791-
// But for MySql, & take precedence on |, wrecking the test for it. So it is needed to write the test in a way
792-
// such as the parenthesis will be preserved.
802+
// When converted to SQL, "undue" parenthesis are stripped out. For most DB, binary operators have same precedence,
803+
// causing "((o.Features | featureSet) & featureSet)" to be equivalent to "o.Features | featureSet & featureSet"
804+
// But for MySql, & take precedence on |, wrecking the test for it. So it is needed to write the test in a way
805+
// such as the parenthesis will be preserved.
793806
where (featureSet & (o.Features | featureSet)) == featureSet
794807
select o).ToListAsync());
795808

@@ -900,9 +913,9 @@ public async Task ContainsOnPersistedCollectionAsync()
900913
var animal = await (session.Query<Animal>().SingleAsync(a => a.SerialNumber == "123"));
901914

902915
var result = await (session.Query<Animal>()
903-
.Where(e => animal.Children.Contains(e.Father))
904-
.OrderBy(e => e.Id)
905-
.FirstOrDefaultAsync());
916+
.Where(e => animal.Children.Contains(e.Father))
917+
.OrderBy(e => e.Id)
918+
.FirstOrDefaultAsync());
906919
Assert.That(result, Is.Not.Null);
907920
Assert.That(result.SerialNumber, Is.EqualTo("1121"));
908921
}
@@ -916,9 +929,9 @@ public async Task CanCompareAggregateResultAsync()
916929
}
917930

918931
await (session.Query<Customer>()
919-
.Select(o => new AggregateDate { Id = o.CustomerId, MaxDate = o.Orders.Max(l => l.RequiredOrderDate)})
920-
.Where(o => o.MaxDate <= DateTime.Today && o.MaxDate >= DateTime.Today)
921-
.ToListAsync());
932+
.Select(o => new AggregateDate { Id = o.CustomerId, MaxDate = o.Orders.Max(l => l.RequiredOrderDate) })
933+
.Where(o => o.MaxDate <= DateTime.Today && o.MaxDate >= DateTime.Today)
934+
.ToListAsync());
922935
}
923936

924937
private class AggregateDate

src/NHibernate.Test/Linq/WhereTests.cs

+41-28
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,11 @@ public void WhereWithConstantExpression()
6060
public void CanUseStringEnumInConditional()
6161
{
6262
var query = db.Users
63-
.Where(
64-
user => (user.Enum1 == EnumStoredAsString.Small
65-
? EnumStoredAsString.Small
66-
: EnumStoredAsString.Large) == user.Enum1)
67-
.Select(x => x.Enum1);
63+
.Where(
64+
user => (user.Enum1 == EnumStoredAsString.Small
65+
? EnumStoredAsString.Small
66+
: EnumStoredAsString.Large) == user.Enum1)
67+
.Select(x => x.Enum1);
6868

6969
Assert.That(query.Count(), Is.GreaterThan(0));
7070
}
@@ -73,11 +73,11 @@ public void CanUseStringEnumInConditional()
7373
public void CanUseStringEnumInConditional2()
7474
{
7575
var query = db.Users
76-
.Where(
77-
user => (user.Enum1 == EnumStoredAsString.Small
78-
? user.Enum1
79-
: EnumStoredAsString.Large) == user.Enum1)
80-
.Select(x => x.Enum1);
76+
.Where(
77+
user => (user.Enum1 == EnumStoredAsString.Small
78+
? user.Enum1
79+
: EnumStoredAsString.Large) == user.Enum1)
80+
.Select(x => x.Enum1);
8181

8282
Assert.That(query.Count(), Is.GreaterThan(0));
8383
}
@@ -317,11 +317,11 @@ public void UsersWithEntityPropertiesThreeLevelsDeep()
317317
var query = from user in db.Users
318318
where user.Role.Entity.Output != null
319319
select new
320-
{
321-
user.Name,
322-
RoleName = user.Role.Name,
323-
user.Role.Entity.Output
324-
};
320+
{
321+
user.Name,
322+
RoleName = user.Role.Name,
323+
user.Role.Entity.Output
324+
};
325325

326326
var list = query.ToList();
327327
Assert.That(list.Count, Is.EqualTo(1));
@@ -510,7 +510,7 @@ public void UsersWithListContains_MutatingListDoesNotBreakOtherSessions()
510510
where names.Contains(user.Name)
511511
select user).ToList();
512512

513-
Assert.AreEqual(2, query.Count);
513+
Assert.AreEqual(2, query.Count);
514514

515515
names.Clear();
516516
}
@@ -680,12 +680,25 @@ public void TimesheetsWithEnumerableContainsOnSelect()
680680

681681
var value = (EnumStoredAsInt32) 1000;
682682
var query = (from sheet in db.Timesheets
683-
where sheet.Users.Select(x => x.NullableEnum2 ?? value).Contains(value)
684-
select sheet).ToList();
683+
where sheet.Users.Select(x => x.NullableEnum2 ?? value).Contains(value)
684+
select sheet).ToList();
685685

686686
Assert.That(query.Count, Is.EqualTo(1));
687687
}
688688

689+
[Test]
690+
public void TimesheetsWithProjectionInSubquery()
691+
{
692+
if (Dialect is MsSqlCeDialect)
693+
Assert.Ignore("Dialect is not supported");
694+
695+
var query = (from sheet in db.Timesheets
696+
where sheet.Users.Select(x => new { Id = x.Id, Name = x.Name }).Any(x => x.Id == 1)
697+
select sheet).ToList();
698+
699+
Assert.That(query.Count, Is.EqualTo(2));
700+
}
701+
689702
[Test]
690703
public void ContainsSubqueryWithCoalesceStringEnumSelect()
691704
{
@@ -787,10 +800,10 @@ public void BitwiseQuery3()
787800
var featureSet = FeatureSet.HasThat;
788801
var query = (
789802
from o in session.Query<User>()
790-
// When converted to SQL, "undue" parenthesis are stripped out. For most DB, binary operators have same precedence,
791-
// causing "((o.Features | featureSet) & featureSet)" to be equivalent to "o.Features | featureSet & featureSet"
792-
// But for MySql, & take precedence on |, wrecking the test for it. So it is needed to write the test in a way
793-
// such as the parenthesis will be preserved.
803+
// When converted to SQL, "undue" parenthesis are stripped out. For most DB, binary operators have same precedence,
804+
// causing "((o.Features | featureSet) & featureSet)" to be equivalent to "o.Features | featureSet & featureSet"
805+
// But for MySql, & take precedence on |, wrecking the test for it. So it is needed to write the test in a way
806+
// such as the parenthesis will be preserved.
794807
where (featureSet & (o.Features | featureSet)) == featureSet
795808
select o).ToList();
796809

@@ -901,9 +914,9 @@ public void ContainsOnPersistedCollection()
901914
var animal = session.Query<Animal>().Single(a => a.SerialNumber == "123");
902915

903916
var result = session.Query<Animal>()
904-
.Where(e => animal.Children.Contains(e.Father))
905-
.OrderBy(e => e.Id)
906-
.FirstOrDefault();
917+
.Where(e => animal.Children.Contains(e.Father))
918+
.OrderBy(e => e.Id)
919+
.FirstOrDefault();
907920
Assert.That(result, Is.Not.Null);
908921
Assert.That(result.SerialNumber, Is.EqualTo("1121"));
909922
}
@@ -917,9 +930,9 @@ public void CanCompareAggregateResult()
917930
}
918931

919932
session.Query<Customer>()
920-
.Select(o => new AggregateDate { Id = o.CustomerId, MaxDate = o.Orders.Max(l => l.RequiredOrderDate)})
921-
.Where(o => o.MaxDate <= DateTime.Today && o.MaxDate >= DateTime.Today)
922-
.ToList();
933+
.Select(o => new AggregateDate { Id = o.CustomerId, MaxDate = o.Orders.Max(l => l.RequiredOrderDate) })
934+
.Where(o => o.MaxDate <= DateTime.Today && o.MaxDate >= DateTime.Today)
935+
.ToList();
923936
}
924937

925938
private class AggregateDate

src/NHibernate/Linq/Visitors/QueryModelVisitor.cs

+1-5
Original file line numberDiff line numberDiff line change
@@ -476,13 +476,9 @@ public override void VisitSelectClause(SelectClause selectClause, QueryModel que
476476

477477
private HqlSelect GetSelectClause(Expression selectClause)
478478
{
479-
if (!_root)
480-
return _hqlTree.TreeBuilder.Select(
481-
HqlGeneratorExpressionVisitor.Visit(selectClause, VisitorParameters).AsExpression());
482-
483479
var visitor = new SelectClauseVisitor(typeof(object[]), VisitorParameters);
484480

485-
visitor.VisitSelector(selectClause);
481+
visitor.VisitSelector(selectClause, !_root);
486482

487483
if (visitor.ProjectionExpression != null)
488484
{

src/NHibernate/Linq/Visitors/SelectClauseNominator.cs

+4-2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class SelectClauseHqlNominator : RelinqExpressionVisitor
3434
public bool ContainsUntranslatedMethodCalls { get; private set; }
3535

3636
private bool _canBeCandidate;
37+
private bool _isSubQuery;
3738
Stack<bool> _stateStack;
3839

3940
public SelectClauseHqlNominator(VisitorParameters parameters)
@@ -43,11 +44,12 @@ public SelectClauseHqlNominator(VisitorParameters parameters)
4344
_parameters = parameters;
4445
}
4546

46-
internal Expression Nominate(Expression expression)
47+
internal Expression Nominate(Expression expression, bool isSubQuery = false)
4748
{
4849
HqlCandidates = new HashSet<Expression>();
4950
ContainsUntranslatedMethodCalls = false;
5051
_canBeCandidate = true;
52+
_isSubQuery = isSubQuery;
5153
_stateStack = new Stack<bool>();
5254
_stateStack.Push(false);
5355

@@ -67,7 +69,7 @@ public override Expression Visit(Expression expression)
6769
return innerExpression;
6870
}
6971

70-
var projectConstantsInHql = _stateStack.Peek() || expression.NodeType == ExpressionType.Equal || IsRegisteredFunction(expression);
72+
var projectConstantsInHql = _stateStack.Peek() || expression.NodeType == ExpressionType.Equal || IsRegisteredFunction(expression) || _isSubQuery;
7173

7274
// Set some flags, unless we already have proper values for them:
7375
// projectConstantsInHql if they are inside a method call executed server side.

src/NHibernate/Linq/Visitors/SelectClauseVisitor.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public IEnumerable<HqlExpression> GetHqlNodes()
3434
return _hqlTreeNodes;
3535
}
3636

37-
public void VisitSelector(Expression expression)
37+
public void VisitSelector(Expression expression, bool isSubQuery = false)
3838
{
3939
var distinct = expression as NhDistinctExpression;
4040
if (distinct != null)
@@ -44,7 +44,7 @@ public void VisitSelector(Expression expression)
4444

4545
// Find the sub trees that can be expressed purely in HQL
4646
var nominator = new SelectClauseHqlNominator(_parameters);
47-
expression = nominator.Nominate(expression);
47+
expression = nominator.Nominate(expression, isSubQuery);
4848
_hqlNodes = nominator.HqlCandidates;
4949

5050
// Linq2SQL ignores calls to local methods. Linq2EF seems to not support

0 commit comments

Comments
 (0)