Skip to content

Commit 5cf0cf4

Browse files
bahusoidhazzik
andauthored
Force join for comparisons in hql when not null entity key can represent null entity (#2081)
Co-authored-by: Alexander Zaytsev <hazzik@gmail.com>
1 parent b81e422 commit 5cf0cf4

File tree

5 files changed

+350
-17
lines changed

5 files changed

+350
-17
lines changed

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

Lines changed: 151 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,124 @@ public async Task EntityJoinFoSubqueryAsync()
153153
}
154154
}
155155

156+
[Test]
157+
public async Task EntityJoinWithNullableOneToOneEntityComparisonInWithClausShouldAddJoinAsync()
158+
{
159+
using (var sqlLog = new SqlLogSpy())
160+
using (var session = OpenSession())
161+
{
162+
var entity =
163+
await (session
164+
.CreateQuery(
165+
"select ex "
166+
+ "from NullableOwner ex "
167+
+ "left join OneToOneEntity st with st = ex.OneToOne "
168+
).SetMaxResults(1)
169+
.UniqueResultAsync<NullableOwner>());
170+
171+
Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "OneToOneEntity").Count, Is.EqualTo(2));
172+
Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected");
173+
}
174+
}
175+
176+
[Test]
177+
public async Task NullableOneToOneWhereEntityIsNotNullAsync()
178+
{
179+
using (var sqlLog = new SqlLogSpy())
180+
using (var session = OpenSession())
181+
{
182+
var entity =
183+
await (session
184+
.CreateQuery(
185+
"select ex "
186+
+ "from NullableOwner ex "
187+
+ "where ex.OneToOne is not null "
188+
).SetMaxResults(1)
189+
.UniqueResultAsync<NullableOwner>());
190+
191+
Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "OneToOneEntity").Count, Is.EqualTo(1));
192+
Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected");
193+
}
194+
}
195+
196+
[Test]
197+
public async Task NullableOneToOneWhereIdIsNotNullAsync()
198+
{
199+
using (var sqlLog = new SqlLogSpy())
200+
using (var session = OpenSession())
201+
{
202+
var entity =
203+
await (session
204+
.CreateQuery(
205+
"select ex "
206+
+ "from NullableOwner ex "
207+
+ "where ex.OneToOne.Id is not null "
208+
).SetMaxResults(1)
209+
.UniqueResultAsync<NullableOwner>());
210+
211+
Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "OneToOneEntity").Count, Is.EqualTo(1));
212+
Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected");
213+
}
214+
}
215+
216+
[Test]
217+
public async Task NullablePropRefWhereIdEntityNotNullShouldAddJoinAsync()
218+
{
219+
using (var sqlLog = new SqlLogSpy())
220+
using (var session = OpenSession())
221+
{
222+
var entity =
223+
await (session
224+
.CreateQuery(
225+
"select ex "
226+
+ "from NullableOwner ex "
227+
+ "where ex.PropRef is not null "
228+
).SetMaxResults(1)
229+
.UniqueResultAsync<NullableOwner>());
230+
231+
Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "PropRefEntity").Count, Is.EqualTo(1));
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 NullableOneToOneFetchQueryIsNotAffectedAsync()
238+
{
239+
using (var sqlLog = new SqlLogSpy())
240+
using (var session = OpenSession())
241+
{
242+
var entity =
243+
await (session
244+
.CreateQuery(
245+
"select ex "
246+
+ "from NullableOwner ex left join fetch ex.OneToOne o "
247+
+ "where o is null "
248+
).SetMaxResults(1)
249+
.UniqueResultAsync<NullableOwner>());
250+
251+
Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "OneToOneEntity").Count, Is.EqualTo(1));
252+
}
253+
}
254+
255+
[Test]
256+
public async Task NullableOneToOneFetchQueryIsNotAffected2Async()
257+
{
258+
using (var sqlLog = new SqlLogSpy())
259+
using (var session = OpenSession())
260+
{
261+
var entity =
262+
await (session
263+
.CreateQuery(
264+
"select ex "
265+
+ "from NullableOwner ex left join fetch ex.OneToOne o "
266+
+ "where o.Id is null "
267+
).SetMaxResults(1)
268+
.UniqueResultAsync<NullableOwner>());
269+
270+
Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "OneToOneEntity").Count, Is.EqualTo(1));
271+
}
272+
}
273+
156274
[Test]
157275
public async Task EntityJoinWithEntityComparisonInWithClausShouldNotAddJoinAsync()
158276
{
@@ -329,7 +447,7 @@ protected override HbmMapping GetMappings()
329447

330448
rc.Property(e => e.Name);
331449
});
332-
450+
333451
mapper.Class<EntityWithCompositeId>(
334452
rc =>
335453
{
@@ -367,6 +485,38 @@ protected override HbmMapping GetMappings()
367485
rc.Property(e => e.Name);
368486
});
369487

488+
mapper.Class<OneToOneEntity>(
489+
rc =>
490+
{
491+
rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb));
492+
rc.Property(e => e.Name);
493+
});
494+
495+
mapper.Class<PropRefEntity>(
496+
rc =>
497+
{
498+
rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb));
499+
rc.Property(e => e.Name);
500+
rc.Property(e => e.PropertyRef);
501+
});
502+
503+
mapper.Class<NullableOwner>(
504+
rc =>
505+
{
506+
rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb));
507+
rc.Property(e => e.Name);
508+
rc.OneToOne(e => e.OneToOne, m => m.Constrained(false));
509+
rc.ManyToOne(
510+
e => e.PropRef,
511+
m =>
512+
{
513+
m.PropertyRef(nameof(PropRefEntity.PropertyRef));
514+
m.ForeignKey("none");
515+
m.NotFound(NotFoundMode.Ignore);
516+
});
517+
});
518+
519+
370520
return mapper.CompileMappingForAllExplicitlyAddedEntities();
371521
}
372522

@@ -431,7 +581,6 @@ protected override void OnSetUp()
431581
Composite1Key2 = _entityWithCompositeId.Key.Id2,
432582
CustomEntityNameId = _entityWithCustomEntityName.Id
433583
};
434-
435584
session.Save(_noAssociation);
436585

437586
session.Flush();

src/NHibernate.Test/Hql/EntityJoinHqlTest.cs

Lines changed: 152 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,124 @@ public void EntityJoinFoSubquery()
142142
}
143143
}
144144

145+
[Test]
146+
public void EntityJoinWithNullableOneToOneEntityComparisonInWithClausShouldAddJoin()
147+
{
148+
using (var sqlLog = new SqlLogSpy())
149+
using (var session = OpenSession())
150+
{
151+
var entity =
152+
session
153+
.CreateQuery(
154+
"select ex "
155+
+ "from NullableOwner ex "
156+
+ "left join OneToOneEntity st with st = ex.OneToOne "
157+
).SetMaxResults(1)
158+
.UniqueResult<NullableOwner>();
159+
160+
Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "OneToOneEntity").Count, Is.EqualTo(2));
161+
Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected");
162+
}
163+
}
164+
165+
[Test]
166+
public void NullableOneToOneWhereEntityIsNotNull()
167+
{
168+
using (var sqlLog = new SqlLogSpy())
169+
using (var session = OpenSession())
170+
{
171+
var entity =
172+
session
173+
.CreateQuery(
174+
"select ex "
175+
+ "from NullableOwner ex "
176+
+ "where ex.OneToOne is not null "
177+
).SetMaxResults(1)
178+
.UniqueResult<NullableOwner>();
179+
180+
Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "OneToOneEntity").Count, Is.EqualTo(1));
181+
Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected");
182+
}
183+
}
184+
185+
[Test]
186+
public void NullableOneToOneWhereIdIsNotNull()
187+
{
188+
using (var sqlLog = new SqlLogSpy())
189+
using (var session = OpenSession())
190+
{
191+
var entity =
192+
session
193+
.CreateQuery(
194+
"select ex "
195+
+ "from NullableOwner ex "
196+
+ "where ex.OneToOne.Id is not null "
197+
).SetMaxResults(1)
198+
.UniqueResult<NullableOwner>();
199+
200+
Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "OneToOneEntity").Count, Is.EqualTo(1));
201+
Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected");
202+
}
203+
}
204+
205+
[Test]
206+
public void NullablePropRefWhereIdEntityNotNullShouldAddJoin()
207+
{
208+
using (var sqlLog = new SqlLogSpy())
209+
using (var session = OpenSession())
210+
{
211+
var entity =
212+
session
213+
.CreateQuery(
214+
"select ex "
215+
+ "from NullableOwner ex "
216+
+ "where ex.PropRef is not null "
217+
).SetMaxResults(1)
218+
.UniqueResult<NullableOwner>();
219+
220+
Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "PropRefEntity").Count, Is.EqualTo(1));
221+
Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected");
222+
}
223+
}
224+
225+
[Test]
226+
public void NullableOneToOneFetchQueryIsNotAffected()
227+
{
228+
using (var sqlLog = new SqlLogSpy())
229+
using (var session = OpenSession())
230+
{
231+
var entity =
232+
session
233+
.CreateQuery(
234+
"select ex "
235+
+ "from NullableOwner ex left join fetch ex.OneToOne o "
236+
+ "where o is null "
237+
).SetMaxResults(1)
238+
.UniqueResult<NullableOwner>();
239+
240+
Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "OneToOneEntity").Count, Is.EqualTo(1));
241+
}
242+
}
243+
244+
[Test]
245+
public void NullableOneToOneFetchQueryIsNotAffected2()
246+
{
247+
using (var sqlLog = new SqlLogSpy())
248+
using (var session = OpenSession())
249+
{
250+
var entity =
251+
session
252+
.CreateQuery(
253+
"select ex "
254+
+ "from NullableOwner ex left join fetch ex.OneToOne o "
255+
+ "where o.Id is null "
256+
).SetMaxResults(1)
257+
.UniqueResult<NullableOwner>();
258+
259+
Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "OneToOneEntity").Count, Is.EqualTo(1));
260+
}
261+
}
262+
145263
[Test]
146264
public void EntityJoinWithEntityComparisonInWithClausShouldNotAddJoin()
147265
{
@@ -334,7 +452,7 @@ protected override HbmMapping GetMappings()
334452

335453
rc.Property(e => e.Name);
336454
});
337-
455+
338456
mapper.Class<EntityWithCompositeId>(
339457
rc =>
340458
{
@@ -372,6 +490,39 @@ protected override HbmMapping GetMappings()
372490
rc.Property(e => e.Name);
373491
});
374492

493+
mapper.Class<OneToOneEntity>(
494+
rc =>
495+
{
496+
rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb));
497+
rc.Property(e => e.Name);
498+
});
499+
500+
mapper.Class<PropRefEntity>(
501+
rc =>
502+
{
503+
rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb));
504+
rc.Property(e => e.Name);
505+
rc.Property(e => e.PropertyRef, m => m.Column("EntityPropertyRef"));
506+
});
507+
508+
mapper.Class<NullableOwner>(
509+
rc =>
510+
{
511+
rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb));
512+
rc.Property(e => e.Name);
513+
rc.OneToOne(e => e.OneToOne, m => m.Constrained(false));
514+
rc.ManyToOne(
515+
e => e.PropRef,
516+
m =>
517+
{
518+
m.Column("OwnerPropertyRef");
519+
m.PropertyRef(nameof(PropRefEntity.PropertyRef));
520+
m.ForeignKey("none");
521+
m.NotFound(NotFoundMode.Ignore);
522+
});
523+
});
524+
525+
375526
return mapper.CompileMappingForAllExplicitlyAddedEntities();
376527
}
377528

@@ -436,7 +587,6 @@ protected override void OnSetUp()
436587
Composite1Key2 = _entityWithCompositeId.Key.Id2,
437588
CustomEntityNameId = _entityWithCustomEntityName.Id
438589
};
439-
440590
session.Save(_noAssociation);
441591

442592
session.Flush();

0 commit comments

Comments
 (0)