diff --git a/src/NHibernate.Test/Async/Hql/EntityJoinHqlTest.cs b/src/NHibernate.Test/Async/Hql/EntityJoinHqlTest.cs index ef552314347..b8c1e497cae 100644 --- a/src/NHibernate.Test/Async/Hql/EntityJoinHqlTest.cs +++ b/src/NHibernate.Test/Async/Hql/EntityJoinHqlTest.cs @@ -349,11 +349,23 @@ public async Task NullableEntityProjectionAsync() from x2 in session.Query() where x == x2 && x.ManyToOne == null && x.OneToOne.Name == null select x2).ToListAsync()); + + var validManyToOne = new OneToOneEntity{Name = "valid"}; + await (session.SaveAsync(validManyToOne)); + nullableOwner2.ManyToOne = validManyToOne; + await (session.FlushAsync()); + + //GH-2988 + var withNullOrValidList = await (session.Query().Where(x => x.ManyToOne.Id == validManyToOne.Id || x.ManyToOne == null).ToListAsync()); + var withNullOrValidList2 = await (session.Query().Where(x => x.ManyToOne == null || x.ManyToOne.Id == validManyToOne.Id).ToListAsync()); + Assert.That(fullList.Count, Is.EqualTo(2)); Assert.That(withValidManyToOneList.Count, Is.EqualTo(0)); Assert.That(withValidManyToOneList2.Count, Is.EqualTo(0)); Assert.That(withNullManyToOneList.Count, Is.EqualTo(2)); Assert.That(withNullManyToOneJoinedList.Count, Is.EqualTo(2)); + Assert.That(withNullOrValidList.Count, Is.EqualTo(2)); + Assert.That(withNullOrValidList2.Count, Is.EqualTo(2)); } } diff --git a/src/NHibernate.Test/Hql/EntityJoinHqlTest.cs b/src/NHibernate.Test/Hql/EntityJoinHqlTest.cs index ac67ccb647e..085b7e6d877 100644 --- a/src/NHibernate.Test/Hql/EntityJoinHqlTest.cs +++ b/src/NHibernate.Test/Hql/EntityJoinHqlTest.cs @@ -337,11 +337,23 @@ public void NullableEntityProjection() from x2 in session.Query() where x == x2 && x.ManyToOne == null && x.OneToOne.Name == null select x2).ToList(); + + var validManyToOne = new OneToOneEntity{Name = "valid"}; + session.Save(validManyToOne); + nullableOwner2.ManyToOne = validManyToOne; + session.Flush(); + + //GH-2988 + var withNullOrValidList = session.Query().Where(x => x.ManyToOne.Id == validManyToOne.Id || x.ManyToOne == null).ToList(); + var withNullOrValidList2 = session.Query().Where(x => x.ManyToOne == null || x.ManyToOne.Id == validManyToOne.Id).ToList(); + Assert.That(fullList.Count, Is.EqualTo(2)); Assert.That(withValidManyToOneList.Count, Is.EqualTo(0)); Assert.That(withValidManyToOneList2.Count, Is.EqualTo(0)); Assert.That(withNullManyToOneList.Count, Is.EqualTo(2)); Assert.That(withNullManyToOneJoinedList.Count, Is.EqualTo(2)); + Assert.That(withNullOrValidList.Count, Is.EqualTo(2)); + Assert.That(withNullOrValidList2.Count, Is.EqualTo(2)); } } diff --git a/src/NHibernate/Engine/JoinSequence.cs b/src/NHibernate/Engine/JoinSequence.cs index 009b3706141..e706d97886e 100644 --- a/src/NHibernate/Engine/JoinSequence.cs +++ b/src/NHibernate/Engine/JoinSequence.cs @@ -43,7 +43,7 @@ private sealed class Join { private readonly IAssociationType associationType; private readonly IJoinable joinable; - private readonly JoinType joinType; + private JoinType joinType; private readonly string alias; private readonly string[] lhsColumns; @@ -75,6 +75,7 @@ public IJoinable Joinable public JoinType JoinType { get { return joinType; } + internal set { joinType = value; } } public string[] LHSColumns @@ -329,5 +330,11 @@ public interface ISelector public ISessionFactoryImplementor Factory => factory; internal bool ForceFilter { get; set; } + + internal void SetJoinType(JoinType joinType) + { + joins[0].JoinType = joinType; + SetUseThetaStyle(false); + } } } diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs index b594f4aad44..5c41ca001c4 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs @@ -417,13 +417,8 @@ private void DereferenceEntity(EntityType entityType, bool implicitJoin, string if ( joinIsNeeded ) { - if (comparisonWithNullableEntity && Walker.IsNullComparison) - { - implicitJoin = false; - _joinType = JoinType.LeftOuterJoin; - } - - DereferenceEntityJoin( classAlias, entityType, implicitJoin, parent ); + var forceLeftJoin = comparisonWithNullableEntity && Walker.IsNullComparison; + DereferenceEntityJoin(classAlias, entityType, implicitJoin, parent, forceLeftJoin); if (comparisonWithNullableEntity) { _columns = FromElement.GetIdentityColumns(); @@ -457,7 +452,7 @@ private void DereferenceEntityIdentifier(string propertyName, DotNode dotParent) } } - private void DereferenceEntityJoin(string classAlias, EntityType propertyType, bool impliedJoin, IASTNode parent) + private void DereferenceEntityJoin(string classAlias, EntityType propertyType, bool impliedJoin, IASTNode parent, bool forceLeftJoin) { _dereferenceType = DerefEntity; if ( Log.IsDebugEnabled() ) @@ -476,7 +471,11 @@ private void DereferenceEntityJoin(string classAlias, EntityType propertyType, b string[] joinColumns = GetColumns(); string joinPath = Path; - if ( impliedJoin && Walker.IsInFrom ) + if (forceLeftJoin) + { + _joinType = JoinType.LeftOuterJoin; + } + else if (impliedJoin && Walker.IsInFrom) { _joinType = Walker.ImpliedJoinType; } @@ -524,7 +523,7 @@ private void DereferenceEntityJoin(string classAlias, EntityType propertyType, b // If this is an implied join in a from element, then use the impled join type which is part of the // tree parser's state (set by the gramamar actions). JoinSequence joinSequence = SessionFactoryHelper - .CreateJoinSequence( impliedJoin, propertyType, tableAlias, _joinType, joinColumns ); + .CreateJoinSequence(!forceLeftJoin && impliedJoin, propertyType, tableAlias, _joinType, joinColumns); var factory = new FromElementFactory( currentFromClause, @@ -545,6 +544,10 @@ private void DereferenceEntityJoin(string classAlias, EntityType propertyType, b } else { + if (forceLeftJoin) + { + elem.JoinSequence.SetJoinType(_joinType); + } currentFromClause.AddDuplicateAlias(classAlias, elem); }