Skip to content

Commit 8c96907

Browse files
committed
Generate correct outer joins in case it is explicit in a subselect
1 parent 93d55c6 commit 8c96907

File tree

5 files changed

+177
-8
lines changed

5 files changed

+177
-8
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System.Collections.Generic;
2+
3+
namespace NHibernate.Test.NHSpecificTest.GH3334
4+
{
5+
public class Entity
6+
{
7+
public virtual int Id { get; set; }
8+
public virtual string Name { get; set; }
9+
public virtual ISet<ChildEntity> Children { get; set; } = new HashSet<ChildEntity>();
10+
public virtual OtherEntity OtherEntity { get; set; }
11+
}
12+
13+
public class ChildEntity
14+
{
15+
public virtual int Id { get; set; }
16+
public virtual string Name { get; set; }
17+
public virtual GrandChildEntity Child { get; set; }
18+
}
19+
20+
public class GrandChildEntity
21+
{
22+
public virtual int Id { get; set; }
23+
public virtual string Name { get; set; }
24+
}
25+
26+
public class OtherEntity
27+
{
28+
public virtual int Id { get; set; }
29+
public virtual string Name { get; set; }
30+
public virtual ISet<Entity> Entities { get; set; } = new HashSet<Entity>();
31+
}
32+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
using NUnit.Framework;
2+
3+
namespace NHibernate.Test.NHSpecificTest.GH3334
4+
{
5+
[TestFixture]
6+
public class Fixture : BugTestCase
7+
{
8+
protected override void OnSetUp()
9+
{
10+
Sfi.Statistics.IsStatisticsEnabled = true;
11+
}
12+
13+
protected override void OnTearDown()
14+
{
15+
Sfi.Statistics.IsStatisticsEnabled = false;
16+
using (var session = OpenSession())
17+
using (var transaction = session.BeginTransaction())
18+
{
19+
session.CreateQuery("delete from ChildEntity").ExecuteUpdate();
20+
session.CreateQuery("delete from GrandChildEntity").ExecuteUpdate();
21+
session.CreateQuery("delete from Entity").ExecuteUpdate();
22+
session.CreateQuery("delete from OtherEntity").ExecuteUpdate();
23+
24+
transaction.Commit();
25+
}
26+
}
27+
28+
[Test]
29+
public void NoExceptionOnExecuteQuery()
30+
{
31+
using (var session = OpenSession())
32+
using (var t = session.BeginTransaction())
33+
{
34+
var parent = new Entity
35+
{
36+
Name = "Parent1",
37+
Children = { new ChildEntity { Name = "Child", Child = new GrandChildEntity { Name = "GrandChild" } } }
38+
};
39+
session.Save(parent);
40+
parent = new Entity
41+
{
42+
Name = "Parent2",
43+
Children = { new ChildEntity { Name = "Child", Child = new GrandChildEntity { Name = "XGrandChild" } } }
44+
};
45+
var other = new OtherEntity { Name = "ABC", Entities = {parent}};
46+
parent.OtherEntity = other;
47+
session.Save(parent);
48+
session.Save(other);
49+
t.Commit();
50+
}
51+
52+
using (var session = OpenSession())
53+
using (var _ = session.BeginTransaction())
54+
{
55+
var q = session.CreateQuery(
56+
@"
57+
SELECT ROOT
58+
FROM Entity AS ROOT
59+
WHERE
60+
EXISTS
61+
(FROM ELEMENTS(ROOT.Children) AS child
62+
LEFT JOIN child.Child AS grandChild
63+
LEFT JOIN ROOT.OtherEntity AS otherEntity
64+
WHERE
65+
grandChild.Name like 'G%'
66+
OR otherEntity.Name like 'G%'
67+
)");
68+
Assert.AreEqual(1, q.List().Count);
69+
70+
q = session.CreateQuery(
71+
@"
72+
SELECT ROOT
73+
FROM Entity AS ROOT
74+
WHERE
75+
EXISTS
76+
(FROM ELEMENTS(ROOT.Children) AS child
77+
LEFT JOIN child.Child AS grandChild
78+
LEFT JOIN ROOT.OtherEntity AS otherEntity
79+
WHERE
80+
grandChild.Name like 'A%'
81+
OR otherEntity.Name like 'A%'
82+
)");
83+
Assert.AreEqual(1, q.List().Count);
84+
85+
/* does not work because of inner join or theta join created for many-to-one
86+
q = session.CreateQuery(
87+
@"
88+
SELECT ROOT
89+
FROM Entity AS ROOT
90+
WHERE
91+
EXISTS
92+
(FROM ELEMENTS(ROOT.Children) AS child
93+
WHERE
94+
child.Child.Name like 'G%'
95+
OR ROOT.OtherEntity.Name like 'A%'
96+
)");
97+
Assert.AreEqual(1, q.List().Count);*/
98+
}
99+
}
100+
}
101+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernate.Test"
3+
namespace="NHibernate.Test.NHSpecificTest.GH3334">
4+
5+
<class name="Entity">
6+
<id name="Id" generator="identity" />
7+
<property name="Name"/>
8+
<set name="Children" cascade="persist,delete,save-update,evict,lock,replicate,delete-orphan">
9+
<key column="Parent" />
10+
<one-to-many class="ChildEntity"/>
11+
</set>
12+
<many-to-one name="OtherEntity" class="OtherEntity"/>
13+
</class>
14+
15+
<class name="ChildEntity">
16+
<id name="Id" generator="identity" />
17+
<property name="Name"/>
18+
<many-to-one name="Child" class="GrandChildEntity" cascade="persist,delete,save-update,evict,lock,replicate,delete-orphan"/>
19+
</class>
20+
21+
<class name="GrandChildEntity">
22+
<id name="Id" generator="identity" />
23+
<property name="Name"/>
24+
</class>
25+
26+
<class name="OtherEntity">
27+
<id name="Id" generator="identity" />
28+
<property name="Name"/>
29+
<set name="Entities" inverse="true" lazy="true">
30+
<key column="OtherEntity" />
31+
<one-to-many class="Entity" />
32+
</set>
33+
</class>
34+
35+
</hibernate-mapping>

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,7 @@ private void DereferenceEntityJoin(string classAlias, EntityType propertyType, b
546546
associatedEntityName,
547547
tableAlias,
548548
joinSequence,
549+
_joinType,
549550
_fetch,
550551
Walker.IsInFrom,
551552
propertyType

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -283,13 +283,13 @@ public FromElement CreateElementJoin(IQueryableCollection queryableCollection)
283283
return elem;
284284
}
285285

286-
public FromElement CreateEntityJoin(
287-
string entityClass,
288-
string tableAlias,
289-
JoinSequence joinSequence,
290-
bool fetchFlag,
291-
bool inFrom,
292-
EntityType type)
286+
public FromElement CreateEntityJoin(string entityClass,
287+
string tableAlias,
288+
JoinSequence joinSequence,
289+
JoinType joinType,
290+
bool fetchFlag,
291+
bool inFrom,
292+
EntityType type)
293293
{
294294
FromElement elem = CreateJoin(entityClass, tableAlias, joinSequence, type, false);
295295
elem.Fetch = fetchFlag;
@@ -315,7 +315,7 @@ public FromElement CreateEntityJoin(
315315
elem.UseFromFragment = true;
316316
elem.SetImpliedInFromClause(true);
317317
}
318-
if (elem.Walker.IsSubQuery)
318+
if (elem.Walker.IsSubQuery && joinType != JoinType.LeftOuterJoin && joinType != JoinType.RightOuterJoin && joinType != JoinType.FullJoin)
319319
{
320320
// two conditions where we need to transform this to a theta-join syntax:
321321
// 1) 'elem' is the "root from-element" in correlated subqueries

0 commit comments

Comments
 (0)