Skip to content

Commit febf050

Browse files
authored
Merge pull request #2889 from bahusoid/nullableIsNull
Fix nullable entity comparison with null Fixes #2611
1 parent f71fe39 commit febf050

File tree

5 files changed

+19
-4
lines changed

5 files changed

+19
-4
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,8 +342,12 @@ public async Task NullableEntityProjectionAsync()
342342

343343
var fullList = await (session.Query<NullableOwner>().Select(x => new {x.Name, ManyToOneId = (Guid?) x.ManyToOne.Id}).ToListAsync());
344344
var withValidManyToOneList = await (session.Query<NullableOwner>().Where(x => x.ManyToOne != null).Select(x => new {x.Name, ManyToOneId = (Guid?) x.ManyToOne.Id}).ToListAsync());
345+
var withValidManyToOneList2 = await (session.CreateQuery("from NullableOwner ex where not ex.ManyToOne is null").ListAsync<NullableOwner>());
346+
var withNullManyToOneList = await (session.Query<NullableOwner>().Where(x => x.ManyToOne == null).ToListAsync());
345347
Assert.That(fullList.Count, Is.EqualTo(2));
346348
Assert.That(withValidManyToOneList.Count, Is.EqualTo(0));
349+
Assert.That(withValidManyToOneList2.Count, Is.EqualTo(0));
350+
Assert.That(withNullManyToOneList.Count, Is.EqualTo(2));
347351
}
348352
}
349353

src/NHibernate.Test/Hql/EntityJoinHqlTest.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,8 +330,12 @@ public void NullableEntityProjection()
330330

331331
var fullList = session.Query<NullableOwner>().Select(x => new {x.Name, ManyToOneId = (Guid?) x.ManyToOne.Id}).ToList();
332332
var withValidManyToOneList = session.Query<NullableOwner>().Where(x => x.ManyToOne != null).Select(x => new {x.Name, ManyToOneId = (Guid?) x.ManyToOne.Id}).ToList();
333+
var withValidManyToOneList2 = session.CreateQuery("from NullableOwner ex where not ex.ManyToOne is null").List<NullableOwner>();
334+
var withNullManyToOneList = session.Query<NullableOwner>().Where(x => x.ManyToOne == null).ToList();
333335
Assert.That(fullList.Count, Is.EqualTo(2));
334336
Assert.That(withValidManyToOneList.Count, Is.EqualTo(0));
337+
Assert.That(withValidManyToOneList2.Count, Is.EqualTo(0));
338+
Assert.That(withNullManyToOneList.Count, Is.EqualTo(2));
335339
}
336340
}
337341

src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public partial class HqlSqlWalker
4242
private SelectClause _selectClause;
4343
private readonly AliasGenerator _aliasGenerator = new AliasGenerator();
4444
private readonly ASTPrinter _printer = new ASTPrinter();
45+
private bool _isNullComparison;
4546

4647
//
4748
//Maps each top-level result variable to its SelectExpression;
@@ -1203,6 +1204,8 @@ public IASTFactory ASTFactory
12031204
}
12041205
}
12051206

1207+
internal bool IsNullComparison => _isNullComparison;
1208+
12061209
public void AddQuerySpaces(string[] spaces)
12071210
{
12081211
for (int i = 0; i < spaces.Length; i++)

src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.g

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ comparisonExpr
380380
| ^(NOT_BETWEEN exprOrSubquery exprOrSubquery exprOrSubquery)
381381
| ^(IN exprOrSubquery inRhs )
382382
| ^(NOT_IN exprOrSubquery inRhs )
383-
| ^(IS_NULL exprOrSubquery)
383+
| ^(IS_NULL { _isNullComparison = true; } exprOrSubquery { _isNullComparison = false; })
384384
| ^(IS_NOT_NULL exprOrSubquery)
385385
// | ^(IS_TRUE expr)
386386
// | ^(IS_FALSE expr)

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -389,8 +389,6 @@ private void DereferenceEntity(EntityType entityType, bool implicitJoin, string
389389
bool joinIsNeeded;
390390

391391
//For nullable entity comparisons we always need to add join (like not constrained one-to-one or not-found ignore associations)
392-
//NOTE: This fix is not fully correct. It doesn't work for comparisons with null (where e.OneToOneProp is null)
393-
// as by default implicit join is generated and to work propelry left join is required (see GH-2611)
394392
bool comparisonWithNullableEntity = entityType.IsNullable && Walker.IsComparativeExpressionClause;
395393

396394
if ( IsDotNode( parent ) )
@@ -417,8 +415,14 @@ private void DereferenceEntity(EntityType entityType, bool implicitJoin, string
417415
|| comparisonWithNullableEntity;
418416
}
419417

420-
if ( joinIsNeeded )
418+
if ( joinIsNeeded )
421419
{
420+
if (comparisonWithNullableEntity && Walker.IsNullComparison)
421+
{
422+
implicitJoin = false;
423+
_joinType = JoinType.LeftOuterJoin;
424+
}
425+
422426
DereferenceEntityJoin( classAlias, entityType, implicitJoin, parent );
423427
if (comparisonWithNullableEntity)
424428
{

0 commit comments

Comments
 (0)