Skip to content

Commit 27ea900

Browse files
bahusoidhazzik
authored andcommitted
Fix query for joins/fetches on entity joins
* Allow With clause with no table references * Adjust With clause processing for entity joins * Fix SQLite test
1 parent 03a19ac commit 27ea900

File tree

7 files changed

+224
-84
lines changed

7 files changed

+224
-84
lines changed

src/NHibernate.Test/Async/Hql/Ast/WithClauseFixture.cs

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
//------------------------------------------------------------------------------
99

1010

11+
using System;
1112
using System.Collections;
13+
using NHibernate.Exceptions;
1214
using NHibernate.Hql.Ast.ANTLR;
1315
using NUnit.Framework;
1416

@@ -52,34 +54,41 @@ public async Task WithClauseFailsWithFetchAsync()
5254
}
5355

5456
[Test]
55-
public async Task InvalidWithSemanticsAsync()
57+
public async Task ValidWithSemanticsAsync()
5658
{
57-
ISession s = OpenSession();
58-
ITransaction txn = s.BeginTransaction();
59-
60-
// PROBLEM : f.bodyWeight is a reference to a column on the Animal table; however, the 'f'
61-
// alias relates to the Human.friends collection which the aonther Human entity. The issue
62-
// here is the way JoinSequence and Joinable (the persister) interact to generate the
63-
// joins relating to the sublcass/superclass tables
64-
Assert.ThrowsAsync<InvalidWithClauseException>(
65-
() =>
66-
s.CreateQuery("from Human h inner join h.friends as f with f.bodyWeight < :someLimit").SetDouble("someLimit", 1).
67-
ListAsync());
68-
69-
Assert.ThrowsAsync<InvalidWithClauseException>(
70-
() =>
71-
s.CreateQuery(
72-
"from Animal a inner join a.offspring o inner join o.mother as m inner join m.father as f with o.bodyWeight > 1").
73-
ListAsync());
74-
75-
Assert.ThrowsAsync<InvalidWithClauseException>(
76-
async () =>
77-
await (s.CreateQuery("from Human h inner join h.offspring o with o.mother.father = :cousin").SetEntity("cousin",
78-
await (s.LoadAsync<Human>(123L)))
79-
.ListAsync()));
59+
using (var s = OpenSession())
60+
{
61+
await (s.CreateQuery(
62+
"from Animal a inner join a.offspring o inner join o.mother as m inner join m.father as f with o.bodyWeight > 1").ListAsync());
63+
}
64+
}
8065

81-
await (txn.CommitAsync());
82-
s.Close();
66+
[Test]
67+
public async Task InvalidWithSemanticsAsync()
68+
{
69+
using (ISession s = OpenSession())
70+
{
71+
// PROBLEM : f.bodyWeight is a reference to a column on the Animal table; however, the 'f'
72+
// alias relates to the Human.friends collection which the aonther Human entity. The issue
73+
// here is the way JoinSequence and Joinable (the persister) interact to generate the
74+
// joins relating to the sublcass/superclass tables
75+
Assert.ThrowsAsync<InvalidWithClauseException>(
76+
() =>
77+
s.CreateQuery("from Human h inner join h.friends as f with f.bodyWeight < :someLimit").SetDouble("someLimit", 1).ListAsync());
78+
79+
//The query below is no longer throw InvalidWithClauseException but generates "invalid" SQL to better support complex with join clauses.
80+
//Invalid SQL means that additional joins for "o.mother.father" are currently added after "offspring" join. Some DBs can process such query and some can't.
81+
try
82+
{
83+
await (s.CreateQuery("from Human h inner join h.offspring o with o.mother.father = :cousin")
84+
.SetInt32("cousin", 123)
85+
.ListAsync());
86+
}
87+
catch (GenericADOException)
88+
{
89+
//Apparently SQLite can process queries with wrong join orders
90+
}
91+
}
8392
}
8493

8594
[Test]
@@ -175,4 +184,4 @@ public TestData(WithClauseFixtureAsync tc)
175184
}
176185
}
177186
}
178-
}
187+
}

src/NHibernate.Test/Async/Hql/EntityJoinHqlTest.cs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,67 @@ public async Task EntityJoinWithEntityAssociationComparison2ShouldAddJoinAsync()
214214
}
215215

216216

217+
[Test]
218+
public async Task WithClauseOnOtherAssociationAsync()
219+
{
220+
using (var sqlLog = new SqlLogSpy())
221+
using (var session = OpenSession())
222+
{
223+
EntityComplex entityComplex =
224+
await (session
225+
.CreateQuery("select ex " +
226+
"from EntityComplex ex join fetch ex.SameTypeChild stc " +
227+
"join ex.SameTypeChild2 stc2 with stc.Version != stc2.Version ")
228+
.SetMaxResults(1)
229+
.UniqueResultAsync<EntityComplex>());
230+
231+
Assert.That(entityComplex, Is.Null);
232+
Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected");
233+
}
234+
}
235+
236+
[Test]
237+
public async Task EntityJoinNoTablesInWithClauseAsync()
238+
{
239+
using (var sqlLog = new SqlLogSpy())
240+
using (var session = OpenSession())
241+
{
242+
EntityComplex entityComplex =
243+
await (session
244+
.CreateQuery("select ex " +
245+
"from EntityWithNoAssociation root " +
246+
"left join EntityComplex ex with 1 = 2")
247+
.SetMaxResults(1)
248+
.UniqueResultAsync<EntityComplex>());
249+
250+
Assert.That(entityComplex, Is.Null);
251+
Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected");
252+
}
253+
}
254+
255+
[Test]
256+
public async Task EntityJoinWithFetchesAsync()
257+
{
258+
using (var sqlLog = new SqlLogSpy())
259+
using (var session = OpenSession())
260+
{
261+
EntityComplex entityComplex =
262+
await (session
263+
.CreateQuery("select ex " +
264+
"from EntityWithNoAssociation root " +
265+
"left join EntityComplex ex with root.Complex1Id = ex.Id " +
266+
"inner join fetch ex.SameTypeChild st")
267+
.SetMaxResults(1)
268+
.UniqueResultAsync<EntityComplex>());
269+
270+
Assert.That(entityComplex, Is.Not.Null);
271+
Assert.That(NHibernateUtil.IsInitialized(entityComplex), Is.True);
272+
Assert.That(entityComplex.SameTypeChild, Is.Not.Null);
273+
Assert.That(NHibernateUtil.IsInitialized(entityComplex.SameTypeChild), Is.True);
274+
Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected");
275+
}
276+
}
277+
217278
[Test, Ignore("Failing for unrelated reasons")]
218279
public async Task CrossJoinAndWithClauseAsync()
219280
{
@@ -248,6 +309,7 @@ protected override HbmMapping GetMappings()
248309

249310
rc.ManyToOne(ep => ep.SameTypeChild, m => m.Column("SameTypeChildId"));
250311

312+
rc.ManyToOne(ep => ep.SameTypeChild2, m => m.Column("SameTypeChild2Id"));
251313

252314
});
253315

@@ -331,7 +393,12 @@ protected override void OnSetUp()
331393
SameTypeChild = new EntityComplex()
332394
{
333395
Name = "ComplexEntityChild"
396+
},
397+
SameTypeChild2 = new EntityComplex()
398+
{
399+
Name = "ComplexEntityChild2"
334400
}
401+
335402
};
336403

337404
_entityWithCompositeId = new EntityWithCompositeId
@@ -345,6 +412,7 @@ protected override void OnSetUp()
345412
};
346413

347414
session.Save(parent.SameTypeChild);
415+
session.Save(parent.SameTypeChild2);
348416
session.Save(parent);
349417
session.Save(_entityWithCompositeId);
350418

src/NHibernate.Test/Hql/Ast/WithClauseFixture.cs

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
using System;
12
using System.Collections;
3+
using NHibernate.Exceptions;
24
using NHibernate.Hql.Ast.ANTLR;
35
using NUnit.Framework;
46

@@ -40,34 +42,41 @@ public void WithClauseFailsWithFetch()
4042
}
4143

4244
[Test]
43-
public void InvalidWithSemantics()
45+
public void ValidWithSemantics()
4446
{
45-
ISession s = OpenSession();
46-
ITransaction txn = s.BeginTransaction();
47-
48-
// PROBLEM : f.bodyWeight is a reference to a column on the Animal table; however, the 'f'
49-
// alias relates to the Human.friends collection which the aonther Human entity. The issue
50-
// here is the way JoinSequence and Joinable (the persister) interact to generate the
51-
// joins relating to the sublcass/superclass tables
52-
Assert.Throws<InvalidWithClauseException>(
53-
() =>
54-
s.CreateQuery("from Human h inner join h.friends as f with f.bodyWeight < :someLimit").SetDouble("someLimit", 1).
55-
List());
56-
57-
Assert.Throws<InvalidWithClauseException>(
58-
() =>
47+
using (var s = OpenSession())
48+
{
5949
s.CreateQuery(
60-
"from Animal a inner join a.offspring o inner join o.mother as m inner join m.father as f with o.bodyWeight > 1").
61-
List());
62-
63-
Assert.Throws<InvalidWithClauseException>(
64-
() =>
65-
s.CreateQuery("from Human h inner join h.offspring o with o.mother.father = :cousin").SetEntity("cousin",
66-
s.Load<Human>(123L))
67-
.List());
50+
"from Animal a inner join a.offspring o inner join o.mother as m inner join m.father as f with o.bodyWeight > 1").List();
51+
}
52+
}
6853

69-
txn.Commit();
70-
s.Close();
54+
[Test]
55+
public void InvalidWithSemantics()
56+
{
57+
using (ISession s = OpenSession())
58+
{
59+
// PROBLEM : f.bodyWeight is a reference to a column on the Animal table; however, the 'f'
60+
// alias relates to the Human.friends collection which the aonther Human entity. The issue
61+
// here is the way JoinSequence and Joinable (the persister) interact to generate the
62+
// joins relating to the sublcass/superclass tables
63+
Assert.Throws<InvalidWithClauseException>(
64+
() =>
65+
s.CreateQuery("from Human h inner join h.friends as f with f.bodyWeight < :someLimit").SetDouble("someLimit", 1).List());
66+
67+
//The query below is no longer throw InvalidWithClauseException but generates "invalid" SQL to better support complex with join clauses.
68+
//Invalid SQL means that additional joins for "o.mother.father" are currently added after "offspring" join. Some DBs can process such query and some can't.
69+
try
70+
{
71+
s.CreateQuery("from Human h inner join h.offspring o with o.mother.father = :cousin")
72+
.SetInt32("cousin", 123)
73+
.List();
74+
}
75+
catch (GenericADOException)
76+
{
77+
//Apparently SQLite can process queries with wrong join orders
78+
}
79+
}
7180
}
7281

7382
[Test]
@@ -163,4 +172,4 @@ public void Cleanup()
163172
}
164173
}
165174
}
166-
}
175+
}

src/NHibernate.Test/Hql/EntityJoinHqlTest.cs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,67 @@ public void EntityJoinWithEntityAssociationComparison2ShouldAddJoin()
203203
}
204204

205205

206+
[Test]
207+
public void WithClauseOnOtherAssociation()
208+
{
209+
using (var sqlLog = new SqlLogSpy())
210+
using (var session = OpenSession())
211+
{
212+
EntityComplex entityComplex =
213+
session
214+
.CreateQuery("select ex " +
215+
"from EntityComplex ex join fetch ex.SameTypeChild stc " +
216+
"join ex.SameTypeChild2 stc2 with stc.Version != stc2.Version ")
217+
.SetMaxResults(1)
218+
.UniqueResult<EntityComplex>();
219+
220+
Assert.That(entityComplex, Is.Null);
221+
Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected");
222+
}
223+
}
224+
225+
[Test]
226+
public void EntityJoinNoTablesInWithClause()
227+
{
228+
using (var sqlLog = new SqlLogSpy())
229+
using (var session = OpenSession())
230+
{
231+
EntityComplex entityComplex =
232+
session
233+
.CreateQuery("select ex " +
234+
"from EntityWithNoAssociation root " +
235+
"left join EntityComplex ex with 1 = 2")
236+
.SetMaxResults(1)
237+
.UniqueResult<EntityComplex>();
238+
239+
Assert.That(entityComplex, Is.Null);
240+
Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected");
241+
}
242+
}
243+
244+
[Test]
245+
public void EntityJoinWithFetches()
246+
{
247+
using (var sqlLog = new SqlLogSpy())
248+
using (var session = OpenSession())
249+
{
250+
EntityComplex entityComplex =
251+
session
252+
.CreateQuery("select ex " +
253+
"from EntityWithNoAssociation root " +
254+
"left join EntityComplex ex with root.Complex1Id = ex.Id " +
255+
"inner join fetch ex.SameTypeChild st")
256+
.SetMaxResults(1)
257+
.UniqueResult<EntityComplex>();
258+
259+
Assert.That(entityComplex, Is.Not.Null);
260+
Assert.That(NHibernateUtil.IsInitialized(entityComplex), Is.True);
261+
Assert.That(entityComplex.SameTypeChild, Is.Not.Null);
262+
Assert.That(NHibernateUtil.IsInitialized(entityComplex.SameTypeChild), Is.True);
263+
Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected");
264+
}
265+
}
266+
206267
[Test, Ignore("Failing for unrelated reasons")]
207268
public void CrossJoinAndWithClause()
208269
{
@@ -237,6 +298,7 @@ protected override HbmMapping GetMappings()
237298

238299
rc.ManyToOne(ep => ep.SameTypeChild, m => m.Column("SameTypeChildId"));
239300

301+
rc.ManyToOne(ep => ep.SameTypeChild2, m => m.Column("SameTypeChild2Id"));
240302

241303
});
242304

@@ -320,7 +382,12 @@ protected override void OnSetUp()
320382
SameTypeChild = new EntityComplex()
321383
{
322384
Name = "ComplexEntityChild"
385+
},
386+
SameTypeChild2 = new EntityComplex()
387+
{
388+
Name = "ComplexEntityChild2"
323389
}
390+
324391
};
325392

326393
_entityWithCompositeId = new EntityWithCompositeId
@@ -334,6 +401,7 @@ protected override void OnSetUp()
334401
};
335402

336403
session.Save(parent.SameTypeChild);
404+
session.Save(parent.SameTypeChild2);
337405
session.Save(parent);
338406
session.Save(_entityWithCompositeId);
339407

src/NHibernate.Test/Hql/EntityJoinHqlTestEntities.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public class EntityComplex
1313
public virtual string LazyProp { get; set; }
1414

1515
public virtual EntityComplex SameTypeChild { get; set; }
16+
public virtual EntityComplex SameTypeChild2 { get; set; }
1617

1718
}
1819

0 commit comments

Comments
 (0)