Skip to content

Commit 0463dae

Browse files
committed
Force join for comparisons with nullable entity properties in hql
1 parent 3e78b69 commit 0463dae

File tree

5 files changed

+350
-15
lines changed

5 files changed

+350
-15
lines changed

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

Lines changed: 152 additions & 3 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
{
@@ -311,7 +429,7 @@ protected override HbmMapping GetMappings()
311429

312430
rc.Property(e => e.Name);
313431
});
314-
432+
315433
mapper.Class<EntityWithCompositeId>(
316434
rc =>
317435
{
@@ -338,7 +456,7 @@ protected override HbmMapping GetMappings()
338456
rc.Property(e => e.Composite1Key1);
339457
rc.Property(e => e.Composite1Key2);
340458
rc.Property(e => e.CustomEntityNameId);
341-
459+
342460
});
343461

344462
mapper.Class<EntityCustomEntityName>(
@@ -350,6 +468,38 @@ protected override HbmMapping GetMappings()
350468
rc.Property(e => e.Name);
351469
});
352470

471+
mapper.Class<OneToOneEntity>(
472+
rc =>
473+
{
474+
rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb));
475+
rc.Property(e => e.Name);
476+
});
477+
478+
mapper.Class<PropRefEntity>(
479+
rc =>
480+
{
481+
rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb));
482+
rc.Property(e => e.Name);
483+
rc.Property(e => e.PropertyRef);
484+
});
485+
486+
mapper.Class<NullableOwner>(
487+
rc =>
488+
{
489+
rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb));
490+
rc.Property(e => e.Name);
491+
rc.OneToOne(e => e.OneToOne, m => m.Constrained(false));
492+
rc.ManyToOne(
493+
e => e.PropRef,
494+
m =>
495+
{
496+
m.PropertyRef(nameof(PropRefEntity.PropertyRef));
497+
m.ForeignKey("none");
498+
m.NotFound(NotFoundMode.Ignore);
499+
});
500+
});
501+
502+
353503
return mapper.CompileMappingForAllExplicitlyAddedEntities();
354504
}
355505

@@ -415,7 +565,6 @@ protected override void OnSetUp()
415565
Composite1Key2 = _entityWithCompositeId.Key.Id2,
416566
CustomEntityNameId = _entityWithCustomEntityName.Id
417567
};
418-
419568
session.Save(_noAssociation);
420569

421570
session.Flush();

src/NHibernate.Test/Hql/EntityJoinHqlTest.cs

Lines changed: 152 additions & 3 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
{
@@ -316,7 +434,7 @@ protected override HbmMapping GetMappings()
316434

317435
rc.Property(e => e.Name);
318436
});
319-
437+
320438
mapper.Class<EntityWithCompositeId>(
321439
rc =>
322440
{
@@ -343,7 +461,7 @@ protected override HbmMapping GetMappings()
343461
rc.Property(e => e.Composite1Key1);
344462
rc.Property(e => e.Composite1Key2);
345463
rc.Property(e => e.CustomEntityNameId);
346-
464+
347465
});
348466

349467
mapper.Class<EntityCustomEntityName>(
@@ -355,6 +473,38 @@ protected override HbmMapping GetMappings()
355473
rc.Property(e => e.Name);
356474
});
357475

476+
mapper.Class<OneToOneEntity>(
477+
rc =>
478+
{
479+
rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb));
480+
rc.Property(e => e.Name);
481+
});
482+
483+
mapper.Class<PropRefEntity>(
484+
rc =>
485+
{
486+
rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb));
487+
rc.Property(e => e.Name);
488+
rc.Property(e => e.PropertyRef);
489+
});
490+
491+
mapper.Class<NullableOwner>(
492+
rc =>
493+
{
494+
rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb));
495+
rc.Property(e => e.Name);
496+
rc.OneToOne(e => e.OneToOne, m => m.Constrained(false));
497+
rc.ManyToOne(
498+
e => e.PropRef,
499+
m =>
500+
{
501+
m.PropertyRef(nameof(PropRefEntity.PropertyRef));
502+
m.ForeignKey("none");
503+
m.NotFound(NotFoundMode.Ignore);
504+
});
505+
});
506+
507+
358508
return mapper.CompileMappingForAllExplicitlyAddedEntities();
359509
}
360510

@@ -420,7 +570,6 @@ protected override void OnSetUp()
420570
Composite1Key2 = _entityWithCompositeId.Key.Id2,
421571
CustomEntityNameId = _entityWithCustomEntityName.Id
422572
};
423-
424573
session.Save(_noAssociation);
425574

426575
session.Flush();

0 commit comments

Comments
 (0)