Skip to content

Commit 59de253

Browse files
committed
NH-3801 - Improve the handling of conditional expressions in select clauses
- Conditional statements are now generated in SQL for select clauses - Case statements in the select clause don't generate unexpected or implicit joins for the test expression - Combined the logic from SelectJoinDetector and ResultOperatorAndOrderByJoinDetector, also handles case statement join logic - Removed most of the changes from NH-3797 (pull request nhibernate#432) as they are now unnecessary because the select clause now does the right thing
1 parent 5f236c3 commit 59de253

13 files changed

+118
-170
lines changed

src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ private void DereferenceEntity(EntityType entityType, bool implicitJoin, string
407407
}
408408
else
409409
{
410-
joinIsNeeded = generateJoin || ( Walker.IsInSelect || Walker.IsInFrom );
410+
joinIsNeeded = generateJoin || ( (Walker.IsInSelect && !Walker.IsInCase ) || Walker.IsInFrom );
411411
}
412412

413413
if ( joinIsNeeded )

src/NHibernate/Linq/GroupBy/AggregatingGroupByRewriter.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4-
using System.Linq.Expressions;
54
using NHibernate.Linq.Clauses;
65
using NHibernate.Linq.ReWriters;
76
using NHibernate.Linq.Visitors;
8-
using NHibernate.Util;
97
using Remotion.Linq;
108
using Remotion.Linq.Clauses.Expressions;
119
using Remotion.Linq.Clauses.ResultOperators;
@@ -44,7 +42,7 @@ public static class AggregatingGroupByRewriter
4442
typeof (CacheableResultOperator)
4543
};
4644

47-
public static void ReWrite(QueryModel queryModel, IList<Expression> groupByKeys)
45+
public static void ReWrite(QueryModel queryModel)
4846
{
4947
var subQueryExpression = queryModel.MainFromClause.FromExpression as SubQueryExpression;
5048

@@ -59,7 +57,6 @@ public static void ReWrite(QueryModel queryModel, IList<Expression> groupByKeys)
5957
var groupBy = operators[0] as GroupResultOperator;
6058
if (groupBy != null)
6159
{
62-
groupBy.ExtractKeyExpressions(groupByKeys);
6360
FlattenSubQuery(queryModel, subQueryExpression.QueryModel, groupBy);
6461
}
6562
}

src/NHibernate/Linq/GroupResultOperatorExtensions.cs

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

src/NHibernate/Linq/ReWriters/AddJoinsReWriter.cs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,37 +15,36 @@ internal interface IIsEntityDecider
1515
public class AddJoinsReWriter : QueryModelVisitorBase, IIsEntityDecider
1616
{
1717
private readonly ISessionFactoryImplementor _sessionFactory;
18-
private readonly SelectJoinDetector _selectJoinDetector;
19-
private readonly ResultOperatorAndOrderByJoinDetector _resultOperatorAndOrderByJoinDetector;
18+
private readonly MemberExpressionJoinDetector _memberExpressionJoinDetector;
2019
private readonly WhereJoinDetector _whereJoinDetector;
2120

2221
private AddJoinsReWriter(ISessionFactoryImplementor sessionFactory, QueryModel queryModel)
2322
{
2423
_sessionFactory = sessionFactory;
2524
var joiner = new Joiner(queryModel);
26-
_selectJoinDetector = new SelectJoinDetector(this, joiner);
27-
_resultOperatorAndOrderByJoinDetector = new ResultOperatorAndOrderByJoinDetector(this, joiner);
25+
_memberExpressionJoinDetector = new MemberExpressionJoinDetector(this, joiner);
2826
_whereJoinDetector = new WhereJoinDetector(this, joiner);
2927
}
3028

31-
public static void ReWrite(QueryModel queryModel, ISessionFactoryImplementor sessionFactory)
29+
public static void ReWrite(QueryModel queryModel, VisitorParameters parameters)
3230
{
33-
new AddJoinsReWriter(sessionFactory, queryModel).VisitQueryModel(queryModel);
31+
var visitor = new AddJoinsReWriter(parameters.SessionFactory, queryModel);
32+
visitor.VisitQueryModel(queryModel);
3433
}
3534

3635
public override void VisitSelectClause(SelectClause selectClause, QueryModel queryModel)
3736
{
38-
_selectJoinDetector.Transform(selectClause);
37+
_memberExpressionJoinDetector.Transform(selectClause);
3938
}
4039

4140
public override void VisitOrdering(Ordering ordering, QueryModel queryModel, OrderByClause orderByClause, int index)
4241
{
43-
_resultOperatorAndOrderByJoinDetector.Transform(ordering);
42+
_memberExpressionJoinDetector.Transform(ordering);
4443
}
4544

4645
public override void VisitResultOperator(ResultOperatorBase resultOperator, QueryModel queryModel, int index)
4746
{
48-
_resultOperatorAndOrderByJoinDetector.Transform(resultOperator);
47+
_memberExpressionJoinDetector.Transform(resultOperator);
4948
}
5049

5150
public override void VisitWhereClause(WhereClause whereClause, QueryModel queryModel, int index)

src/NHibernate/Linq/Visitors/JoinBuilder.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,14 @@ public void MakeInnerIfJoined(string key)
6060
public bool CanAddJoin(Expression expression)
6161
{
6262
var source = QuerySourceExtractor.GetQuerySource(expression);
63-
64-
if (_queryModel.MainFromClause == source)
63+
64+
if (_queryModel.MainFromClause == source)
6565
return true;
66-
66+
6767
var bodyClause = source as IBodyClause;
68-
if (bodyClause != null && _queryModel.BodyClauses.Contains(bodyClause))
68+
if (bodyClause != null && _queryModel.BodyClauses.Contains(bodyClause))
6969
return true;
70-
70+
7171
var resultOperatorBase = source as ResultOperatorBase;
7272
return resultOperatorBase != null && _queryModel.ResultOperators.Contains(resultOperatorBase);
7373
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
using System.Collections;
2+
using System.Collections.Generic;
3+
using System.Linq.Expressions;
4+
using NHibernate.Linq.ReWriters;
5+
using Remotion.Linq.Clauses;
6+
using Remotion.Linq.Clauses.Expressions;
7+
using Remotion.Linq.Parsing;
8+
9+
namespace NHibernate.Linq.Visitors
10+
{
11+
/// <summary>
12+
/// Detects joins in Select, OrderBy and Results (GroupBy) clauses.
13+
/// Replaces them with appropriate joins, maintaining reference equality between different clauses.
14+
/// This allows extracted GroupBy key expression to also be replaced so that they can continue to match replaced Select expressions
15+
/// </summary>
16+
internal class MemberExpressionJoinDetector : ExpressionTreeVisitor
17+
{
18+
private readonly IIsEntityDecider _isEntityDecider;
19+
private readonly IJoiner _joiner;
20+
21+
private bool _requiresJoinForNonIdentifier;
22+
private bool _hasIdentifier;
23+
private int _memberExpressionDepth;
24+
25+
public MemberExpressionJoinDetector(IIsEntityDecider isEntityDecider, IJoiner joiner)
26+
{
27+
_isEntityDecider = isEntityDecider;
28+
_joiner = joiner;
29+
}
30+
31+
protected override Expression VisitMemberExpression(MemberExpression expression)
32+
{
33+
var isIdentifier = _isEntityDecider.IsIdentifier(expression.Expression.Type, expression.Member.Name);
34+
if (isIdentifier)
35+
_hasIdentifier = true;
36+
if (!isIdentifier)
37+
_memberExpressionDepth++;
38+
39+
var result = base.VisitMemberExpression(expression);
40+
41+
if (!isIdentifier)
42+
_memberExpressionDepth--;
43+
44+
if (_isEntityDecider.IsEntity(expression.Type) &&
45+
((_requiresJoinForNonIdentifier && !_hasIdentifier) || _memberExpressionDepth > 0) &&
46+
_joiner.CanAddJoin(expression))
47+
{
48+
var key = ExpressionKeyVisitor.Visit(expression, null);
49+
return _joiner.AddJoin(result, key);
50+
}
51+
52+
return result;
53+
}
54+
55+
protected override Expression VisitSubQueryExpression(SubQueryExpression expression)
56+
{
57+
expression.QueryModel.TransformExpressions(VisitExpression);
58+
return expression;
59+
}
60+
61+
protected override Expression VisitConditionalExpression(ConditionalExpression expression)
62+
{
63+
var oldRequiresJoinForNonIdentifier = _requiresJoinForNonIdentifier;
64+
_requiresJoinForNonIdentifier = false;
65+
var newTest = VisitExpression(expression.Test);
66+
_requiresJoinForNonIdentifier = oldRequiresJoinForNonIdentifier;
67+
var newFalse = VisitExpression(expression.IfFalse);
68+
var newTrue = VisitExpression(expression.IfTrue);
69+
if ((newTest != expression.Test) || (newFalse != expression.IfFalse) || (newTrue != expression.IfTrue))
70+
return Expression.Condition(newTest, newTrue, newFalse);
71+
return expression;
72+
}
73+
74+
public void Transform(SelectClause selectClause)
75+
{
76+
_requiresJoinForNonIdentifier = true;
77+
selectClause.TransformExpressions(VisitExpression);
78+
_requiresJoinForNonIdentifier = false;
79+
}
80+
81+
public void Transform(ResultOperatorBase resultOperator)
82+
{
83+
resultOperator.TransformExpressions(VisitExpression);
84+
}
85+
86+
public void Transform(Ordering ordering)
87+
{
88+
ordering.TransformExpressions(VisitExpression);
89+
}
90+
}
91+
}

src/NHibernate/Linq/Visitors/QueryModelVisitor.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public static ExpressionToHqlTranslationResults GenerateHqlQuery(QueryModel quer
3434
NonAggregatingGroupByRewriter.ReWrite(queryModel);
3535

3636
// Rewrite aggregate group-by statements
37-
AggregatingGroupByRewriter.ReWrite(queryModel, parameters.GroupByKeys);
37+
AggregatingGroupByRewriter.ReWrite(queryModel);
3838

3939
// Rewrite aggregating group-joins
4040
AggregatingGroupJoinRewriter.ReWrite(queryModel);
@@ -57,7 +57,7 @@ public static ExpressionToHqlTranslationResults GenerateHqlQuery(QueryModel quer
5757
ArrayIndexExpressionFlattener.ReWrite(queryModel);
5858

5959
// Add joins for references
60-
AddJoinsReWriter.ReWrite(queryModel, parameters.SessionFactory);
60+
AddJoinsReWriter.ReWrite(queryModel, parameters);
6161

6262
// Move OrderBy clauses to end
6363
MoveOrderByToEndRewriter.ReWrite(queryModel);
@@ -238,7 +238,7 @@ public override void VisitSelectClause(SelectClause selectClause, QueryModel que
238238
{
239239
CurrentEvaluationType = selectClause.GetOutputDataInfo();
240240

241-
var visitor = new SelectClauseVisitor(typeof(object[]), VisitorParameters, VisitorParameters.GroupByKeys);
241+
var visitor = new SelectClauseVisitor(typeof(object[]), VisitorParameters);
242242

243243
visitor.Visit(selectClause.Selector);
244244

src/NHibernate/Linq/Visitors/ResultOperatorAndOrderByJoinDetector.cs

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

src/NHibernate/Linq/Visitors/SelectClauseNominator.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ public override Expression VisitExpression(Expression expression)
6464
ContainsUntranslatedMethodCalls = ContainsUntranslatedMethodCalls || !isRegisteredFunction;
6565
}
6666

67+
// Attempt to project conditionals to the DB to prevent inner joins, or more projection work on result than is needed
68+
if (expression != null && expression.NodeType == ExpressionType.Conditional)
69+
{
70+
projectConstantsInHql = true;
71+
}
72+
6773
_stateStack.Push(projectConstantsInHql);
6874

6975
if (expression == null)

src/NHibernate/Linq/Visitors/SelectClauseVisitor.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,11 @@ public class SelectClauseVisitor : ExpressionTreeVisitor
1818
private List<HqlExpression> _hqlTreeNodes = new List<HqlExpression>();
1919
private readonly HqlGeneratorExpressionTreeVisitor _hqlVisitor;
2020

21-
public SelectClauseVisitor(System.Type inputType, VisitorParameters parameters, IEnumerable<Expression> groupByKeys)
21+
public SelectClauseVisitor(System.Type inputType, VisitorParameters parameters)
2222
{
2323
_inputParameter = Expression.Parameter(inputType, "input");
2424
_parameters = parameters;
2525
_hqlVisitor = new HqlGeneratorExpressionTreeVisitor(_parameters);
26-
_hqlNodes = new HashSet<Expression>(groupByKeys);
2726
}
2827

2928
public LambdaExpression ProjectionExpression { get; private set; }
@@ -44,7 +43,7 @@ public void Visit(Expression expression)
4443
// Find the sub trees that can be expressed purely in HQL
4544
var nominator = new SelectClauseHqlNominator(_parameters);
4645
nominator.Visit(expression);
47-
_hqlNodes.UnionWith(nominator.HqlCandidates);
46+
_hqlNodes = nominator.HqlCandidates;
4847

4948
// Linq2SQL ignores calls to local methods. Linq2EF seems to not support
5049
// calls to local methods at all. For NHibernate we support local methods,

0 commit comments

Comments
 (0)