Skip to content

Commit aff0871

Browse files
bahusoidhazzik
authored andcommitted
Force join for comparisons in hql when not null entity key can represent null entity (nhibernate#2081)
Co-authored-by: Alexander Zaytsev <[email protected]>
1 parent f8a3a22 commit aff0871

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
@@ -145,6 +145,124 @@ public void EntityJoinFoSubquery()
145145
}
146146
}
147147

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

375493
rc.Property(e => e.Name);
376494
});
377-
495+
378496
mapper.Class<EntityWithCompositeId>(
379497
rc =>
380498
{
@@ -412,6 +530,39 @@ protected override HbmMapping GetMappings()
412530
rc.Property(e => e.Name);
413531
});
414532

533+
mapper.Class<OneToOneEntity>(
534+
rc =>
535+
{
536+
rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb));
537+
rc.Property(e => e.Name);
538+
});
539+
540+
mapper.Class<PropRefEntity>(
541+
rc =>
542+
{
543+
rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb));
544+
rc.Property(e => e.Name);
545+
rc.Property(e => e.PropertyRef, m => m.Column("EntityPropertyRef"));
546+
});
547+
548+
mapper.Class<NullableOwner>(
549+
rc =>
550+
{
551+
rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb));
552+
rc.Property(e => e.Name);
553+
rc.OneToOne(e => e.OneToOne, m => m.Constrained(false));
554+
rc.ManyToOne(
555+
e => e.PropRef,
556+
m =>
557+
{
558+
m.Column("OwnerPropertyRef");
559+
m.PropertyRef(nameof(PropRefEntity.PropertyRef));
560+
m.ForeignKey("none");
561+
m.NotFound(NotFoundMode.Ignore);
562+
});
563+
});
564+
565+
415566

416567
Node.AddMapping(mapper);
417568
UserEntityVisit.AddMapping(mapper);
@@ -480,7 +631,6 @@ protected override void OnSetUp()
480631
Composite1Key2 = _entityWithCompositeId.Key.Id2,
481632
CustomEntityNameId = _entityWithCustomEntityName.Id
482633
};
483-
484634
session.Save(_noAssociation);
485635

486636
session.Flush();

0 commit comments

Comments
 (0)