Skip to content

Commit f98c463

Browse files
authored
Add support for table group joins for subclasses in Criteria (#2545)
Fixes #1209, fixes #1215
1 parent ef4f4a4 commit f98c463

File tree

9 files changed

+187
-127
lines changed

9 files changed

+187
-127
lines changed

src/NHibernate.Test/Async/NHSpecificTest/NH2049/Fixture2049.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,12 @@ protected override void OnTearDown()
4848
}
4949

5050
[Test]
51-
[KnownBug("Known bug NH-2049.")]
5251
public async Task CanCriteriaQueryWithFilterOnJoinClassBaseClassPropertyAsync()
5352
{
5453
using (ISession session = OpenSession())
5554
{
5655
session.EnableFilter("DeletedCustomer").SetParameter("deleted", false);
57-
IList<Person> persons = await (session.CreateCriteria(typeof (Person)).ListAsync<Person>());
56+
IList<Person> persons = await (session.QueryOver<Person>().JoinQueryOver(x => x.IndividualCustomer).ListAsync<Person>());
5857

5958
Assert.That(persons, Has.Count.EqualTo(1));
6059
Assert.That(persons[0].Id, Is.EqualTo(1));

src/NHibernate.Test/Async/NHSpecificTest/NH2208/Filter.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,23 @@ namespace NHibernate.Test.NHSpecificTest.NH2208
1717
public class FilterAsync : BugTestCase
1818
{
1919
[Test]
20-
public async Task TestAsync()
20+
public async Task TestHqlAsync()
2121
{
2222
using (ISession session = OpenSession())
2323
{
2424
session.EnableFilter("myfilter");
2525
await (session.CreateQuery("from E1 e join fetch e.BO").ListAsync());
2626
}
2727
}
28+
29+
[Test]
30+
public async Task TestQueryOverAsync()
31+
{
32+
using (ISession session = OpenSession())
33+
{
34+
session.EnableFilter("myfilter");
35+
await (session.QueryOver<E1>().JoinQueryOver(x => x.BO).ListAsync());
36+
}
37+
}
2838
}
2939
}

src/NHibernate.Test/NHSpecificTest/NH2049/Fixture2049.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,12 @@ protected override void OnTearDown()
3737
}
3838

3939
[Test]
40-
[KnownBug("Known bug NH-2049.")]
4140
public void CanCriteriaQueryWithFilterOnJoinClassBaseClassProperty()
4241
{
4342
using (ISession session = OpenSession())
4443
{
4544
session.EnableFilter("DeletedCustomer").SetParameter("deleted", false);
46-
IList<Person> persons = session.CreateCriteria(typeof (Person)).List<Person>();
45+
IList<Person> persons = session.QueryOver<Person>().JoinQueryOver(x => x.IndividualCustomer).List<Person>();
4746

4847
Assert.That(persons, Has.Count.EqualTo(1));
4948
Assert.That(persons[0].Id, Is.EqualTo(1));

src/NHibernate.Test/NHSpecificTest/NH2208/Filter.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,23 @@ namespace NHibernate.Test.NHSpecificTest.NH2208
66
public class Filter : BugTestCase
77
{
88
[Test]
9-
public void Test()
9+
public void TestHql()
1010
{
1111
using (ISession session = OpenSession())
1212
{
1313
session.EnableFilter("myfilter");
1414
session.CreateQuery("from E1 e join fetch e.BO").List();
1515
}
1616
}
17+
18+
[Test]
19+
public void TestQueryOver()
20+
{
21+
using (ISession session = OpenSession())
22+
{
23+
session.EnableFilter("myfilter");
24+
session.QueryOver<E1>().JoinQueryOver(x => x.BO).List();
25+
}
26+
}
1727
}
1828
}

src/NHibernate/Engine/IJoin.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using NHibernate.Persister.Entity;
2+
using NHibernate.SqlCommand;
3+
using NHibernate.Type;
4+
5+
namespace NHibernate.Engine
6+
{
7+
internal interface IJoin
8+
{
9+
IJoinable Joinable { get; }
10+
string[] LHSColumns { get; }
11+
string Alias { get; }
12+
IAssociationType AssociationType { get; }
13+
JoinType JoinType { get; }
14+
}
15+
}

src/NHibernate/Engine/JoinSequence.cs

Lines changed: 3 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Linq;
43
using System.Text;
54
using NHibernate.Hql.Ast.ANTLR.Tree;
65
using NHibernate.Persister.Collection;
@@ -41,7 +40,7 @@ public override string ToString()
4140
return buf.Append('}').ToString();
4241
}
4342

44-
private sealed class Join
43+
private sealed class Join : IJoin
4544
{
4645
private readonly IAssociationType associationType;
4746
private readonly IJoinable joinable;
@@ -50,7 +49,7 @@ private sealed class Join
5049
private readonly string[] lhsColumns;
5150

5251
public Join(ISessionFactoryImplementor factory, IAssociationType associationType, string alias, JoinType joinType,
53-
string[] lhsColumns)
52+
string[] lhsColumns)
5453
{
5554
this.associationType = associationType;
5655
this.joinable = associationType.GetAssociatedJoinable(factory);
@@ -182,7 +181,7 @@ internal JoinFragment ToJoinFragment(
182181
last = join.Joinable;
183182
}
184183

185-
if (rootJoinable == null && ProcessAsTableGroupJoin(includeAllSubclassJoins, withClauses, joinFragment))
184+
if (rootJoinable == null && TableGroupJoinHelper.ProcessAsTableGroupJoin(joins, withClauses, includeAllSubclassJoins, joinFragment, alias => IsIncluded(alias), factory))
186185
{
187186
return joinFragment;
188187
}
@@ -253,117 +252,6 @@ private SqlString GetWithClause(IDictionary<string, IFilter> enabledFilters, ref
253252
return SqlStringHelper.JoinParts(" and ", withConditions);
254253
}
255254

256-
private bool ProcessAsTableGroupJoin(bool includeAllSubclassJoins, SqlString[] withClauseFragments, JoinFragment joinFragment)
257-
{
258-
if (!NeedsTableGroupJoin(joins, withClauseFragments, includeAllSubclassJoins))
259-
return false;
260-
261-
var first = joins[0];
262-
string joinString = ANSIJoinFragment.GetJoinString(first.JoinType);
263-
joinFragment.AddFromFragmentString(
264-
new SqlString(
265-
joinString,
266-
" (",
267-
first.Joinable.TableName,
268-
" ",
269-
first.Alias
270-
));
271-
272-
foreach (var join in joins)
273-
{
274-
if (join != first)
275-
joinFragment.AddJoin(
276-
join.Joinable.TableName,
277-
join.Alias,
278-
join.LHSColumns,
279-
JoinHelper.GetRHSColumnNames(join.AssociationType, factory),
280-
join.JoinType,
281-
SqlString.Empty);
282-
283-
AddSubclassJoins(
284-
joinFragment,
285-
join.Alias,
286-
join.Joinable,
287-
// TODO (from hibernate): Think about if this could be made always true
288-
// NH Specific: made always true (original check: join.JoinType == JoinType.InnerJoin)
289-
true,
290-
includeAllSubclassJoins
291-
);
292-
}
293-
294-
var tableGroupWithClause = GetTableGroupJoinWithClause(withClauseFragments, first);
295-
joinFragment.AddFromFragmentString(tableGroupWithClause);
296-
return true;
297-
}
298-
299-
private SqlString GetTableGroupJoinWithClause(SqlString[] withClauseFragments, Join first)
300-
{
301-
SqlStringBuilder fromFragment = new SqlStringBuilder();
302-
fromFragment.Add(")").Add(" on ");
303-
304-
String[] lhsColumns = first.LHSColumns;
305-
var isAssociationJoin = lhsColumns.Length > 0;
306-
if (isAssociationJoin)
307-
{
308-
String rhsAlias = first.Alias;
309-
String[] rhsColumns = JoinHelper.GetRHSColumnNames(first.AssociationType, factory);
310-
for (int j = 0; j < lhsColumns.Length; j++)
311-
{
312-
fromFragment.Add(lhsColumns[j]);
313-
fromFragment.Add("=");
314-
fromFragment.Add(rhsAlias);
315-
fromFragment.Add(".");
316-
fromFragment.Add(rhsColumns[j]);
317-
if (j < lhsColumns.Length - 1)
318-
{
319-
fromFragment.Add(" and ");
320-
}
321-
}
322-
}
323-
324-
for (var i= 0; i < withClauseFragments.Length; i++)
325-
{
326-
var withClause = withClauseFragments[i];
327-
if (SqlStringHelper.IsEmpty(withClause))
328-
continue;
329-
330-
if (withClause.StartsWithCaseInsensitive(" and "))
331-
{
332-
if (!isAssociationJoin)
333-
{
334-
withClause = withClause.Substring(4);
335-
}
336-
}
337-
else if (isAssociationJoin)
338-
{
339-
fromFragment.Add(" and ");
340-
}
341-
342-
fromFragment.Add(withClause);
343-
}
344-
345-
return fromFragment.ToSqlString();
346-
}
347-
348-
private bool NeedsTableGroupJoin(List<Join> joins, SqlString[] withClauseFragments, bool includeSubclasses)
349-
{
350-
// If the rewrite is disabled or we don't have a with clause, we don't need a table group join
351-
if ( /*!collectionJoinSubquery ||*/ withClauseFragments.All(x => SqlStringHelper.IsEmpty(x)))
352-
{
353-
return false;
354-
}
355-
// If we only have one join, a table group join is only necessary if subclass columns are used in the with clause
356-
if (joins.Count == 1)
357-
{
358-
return joins[0].Joinable is AbstractEntityPersister persister && persister.HasSubclassJoins(includeSubclasses);
359-
//NH Specific: No alias processing
360-
//return isSubclassAliasDereferenced( joins[ 0], withClauseFragment );
361-
}
362-
363-
//NH Specific: No alias processing (see hibernate JoinSequence.NeedsTableGroupJoin)
364-
return true;
365-
}
366-
367255
private bool IsManyToManyRoot(IJoinable joinable)
368256
{
369257
if (joinable != null && joinable.IsCollection)
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using NHibernate.Persister.Entity;
5+
using NHibernate.SqlCommand;
6+
7+
namespace NHibernate.Engine
8+
{
9+
//Generates table group join if neccessary. Example of generated query with table group join:
10+
// SELECT *
11+
// FROM Person person0_
12+
// INNER JOIN (
13+
// IndividualCustomer individual1_
14+
// INNER JOIN Customer individual1_1_ ON individual1_.IndividualCustomerID = individual1_1_.Id
15+
// ) ON person0_.Id = individual1_.PersonID AND individual1_1_.Deleted = @p0
16+
internal class TableGroupJoinHelper
17+
{
18+
internal static bool ProcessAsTableGroupJoin(IReadOnlyList<IJoin> tableGroupJoinables, SqlString[] withClauseFragments, bool includeAllSubclassJoins, JoinFragment joinFragment, Func<string, bool> isSubclassIncluded, ISessionFactoryImplementor sessionFactoryImplementor)
19+
{
20+
if (!NeedsTableGroupJoin(tableGroupJoinables, withClauseFragments, includeAllSubclassJoins))
21+
return false;
22+
23+
var first = tableGroupJoinables[0];
24+
string joinString = ANSIJoinFragment.GetJoinString(first.JoinType);
25+
joinFragment.AddFromFragmentString(
26+
new SqlString(
27+
joinString,
28+
" (",
29+
first.Joinable.TableName,
30+
" ",
31+
first.Alias
32+
));
33+
34+
foreach (var join in tableGroupJoinables)
35+
{
36+
if (join != first)
37+
joinFragment.AddJoin(
38+
join.Joinable.TableName,
39+
join.Alias,
40+
join.LHSColumns,
41+
JoinHelper.GetRHSColumnNames(join.AssociationType, sessionFactoryImplementor),
42+
join.JoinType,
43+
SqlString.Empty);
44+
45+
bool include = includeAllSubclassJoins && isSubclassIncluded(join.Alias);
46+
// TODO (from hibernate): Think about if this could be made always true
47+
// NH Specific: made always true (original check: join.JoinType == JoinType.InnerJoin)
48+
const bool innerJoin = true;
49+
joinFragment.AddJoins(
50+
join.Joinable.FromJoinFragment(join.Alias, innerJoin, include),
51+
join.Joinable.WhereJoinFragment(join.Alias, innerJoin, include));
52+
}
53+
54+
var withClause = GetTableGroupJoinWithClause(withClauseFragments, first, sessionFactoryImplementor);
55+
joinFragment.AddFromFragmentString(withClause);
56+
return true;
57+
}
58+
59+
private static bool NeedsTableGroupJoin(IReadOnlyList<IJoin> joins, SqlString[] withClauseFragments, bool includeSubclasses)
60+
{
61+
// If we don't have a with clause, we don't need a table group join
62+
if (withClauseFragments.All(x => SqlStringHelper.IsEmpty(x)))
63+
{
64+
return false;
65+
}
66+
67+
// If we only have one join, a table group join is only necessary if subclass columns are used in the with clause
68+
if (joins.Count == 1)
69+
{
70+
return joins[0].Joinable is AbstractEntityPersister persister && persister.HasSubclassJoins(includeSubclasses);
71+
//NH Specific: No alias processing
72+
//return isSubclassAliasDereferenced( joins[ 0], withClauseFragment );
73+
}
74+
75+
//NH Specific: No alias processing (see hibernate JoinSequence.NeedsTableGroupJoin)
76+
return true;
77+
}
78+
79+
private static SqlString GetTableGroupJoinWithClause(SqlString[] withClauseFragments, IJoin first, ISessionFactoryImplementor factory)
80+
{
81+
SqlStringBuilder fromFragment = new SqlStringBuilder();
82+
fromFragment.Add(")").Add(" on ");
83+
84+
string[] lhsColumns = first.LHSColumns;
85+
var isAssociationJoin = lhsColumns.Length > 0;
86+
if (isAssociationJoin)
87+
{
88+
string rhsAlias = first.Alias;
89+
string[] rhsColumns = JoinHelper.GetRHSColumnNames(first.AssociationType, factory);
90+
fromFragment.Add(lhsColumns[0]).Add("=").Add(rhsAlias).Add(".").Add(rhsColumns[0]);
91+
for (int j = 1; j < lhsColumns.Length; j++)
92+
{
93+
fromFragment.Add(" and ").Add(lhsColumns[j]).Add("=").Add(rhsAlias).Add(".").Add(rhsColumns[j]);
94+
}
95+
}
96+
97+
AppendWithClause(fromFragment, isAssociationJoin, withClauseFragments);
98+
99+
return fromFragment.ToSqlString();
100+
}
101+
102+
private static void AppendWithClause(SqlStringBuilder fromFragment, bool hasConditions, SqlString[] withClauseFragments)
103+
{
104+
for (var i = 0; i < withClauseFragments.Length; i++)
105+
{
106+
var withClause = withClauseFragments[i];
107+
if (SqlStringHelper.IsEmpty(withClause))
108+
continue;
109+
110+
if (withClause.StartsWithCaseInsensitive(" and "))
111+
{
112+
if (!hasConditions)
113+
{
114+
withClause = withClause.Substring(4);
115+
}
116+
}
117+
else if (hasConditions)
118+
{
119+
fromFragment.Add(" and ");
120+
}
121+
122+
fromFragment.Add(withClause);
123+
hasConditions = true;
124+
}
125+
}
126+
}
127+
}

0 commit comments

Comments
 (0)