From 819ccfb0e51aa1f3b5a3e4ab561e7263b19cd7a9 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Fri, 14 Jan 2022 13:45:30 +0200 Subject: [PATCH 1/3] Test case --- src/NHibernate.Test/Async/Hql/EntityJoinHqlTest.cs | 12 ++++++++++++ src/NHibernate.Test/Hql/EntityJoinHqlTest.cs | 12 ++++++++++++ 2 files changed, 24 insertions(+) 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)); } } From b3ce5106d71a4b78a6c8de6b623afaf3126fa010 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Fri, 14 Jan 2022 13:51:15 +0200 Subject: [PATCH 2/3] Fix --- src/NHibernate/Engine/JoinSequence.cs | 9 ++++++++- src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs | 15 ++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) 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..c44dc62604c 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs @@ -417,13 +417,14 @@ private void DereferenceEntity(EntityType entityType, bool implicitJoin, string if ( joinIsNeeded ) { + bool forceJoinType = false; if (comparisonWithNullableEntity && Walker.IsNullComparison) { - implicitJoin = false; _joinType = JoinType.LeftOuterJoin; + forceJoinType = true; } - DereferenceEntityJoin( classAlias, entityType, implicitJoin, parent ); + DereferenceEntityJoin(classAlias, entityType, implicitJoin, parent, forceJoinType); if (comparisonWithNullableEntity) { _columns = FromElement.GetIdentityColumns(); @@ -457,7 +458,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 forceJoinType) { _dereferenceType = DerefEntity; if ( Log.IsDebugEnabled() ) @@ -476,7 +477,7 @@ private void DereferenceEntityJoin(string classAlias, EntityType propertyType, b string[] joinColumns = GetColumns(); string joinPath = Path; - if ( impliedJoin && Walker.IsInFrom ) + if (impliedJoin && !forceJoinType && Walker.IsInFrom) { _joinType = Walker.ImpliedJoinType; } @@ -524,7 +525,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(!forceJoinType && impliedJoin, propertyType, tableAlias, _joinType, joinColumns); var factory = new FromElementFactory( currentFromClause, @@ -545,6 +546,10 @@ private void DereferenceEntityJoin(string classAlias, EntityType propertyType, b } else { + if (forceJoinType) + { + elem.JoinSequence.SetJoinType(_joinType); + } currentFromClause.AddDuplicateAlias(classAlias, elem); } From 93be059eba64148ecc36b46da84bb7a5589fef87 Mon Sep 17 00:00:00 2001 From: Alex Zaytsev Date: Sat, 15 Jan 2022 11:41:38 +1300 Subject: [PATCH 3/3] Move setting _joinType to LeftOuterJoin to DereferenceEntityJoin so the logic is semi-contained --- src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs | 22 +++++++++----------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs index c44dc62604c..5c41ca001c4 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs @@ -417,14 +417,8 @@ private void DereferenceEntity(EntityType entityType, bool implicitJoin, string if ( joinIsNeeded ) { - bool forceJoinType = false; - if (comparisonWithNullableEntity && Walker.IsNullComparison) - { - _joinType = JoinType.LeftOuterJoin; - forceJoinType = true; - } - - DereferenceEntityJoin(classAlias, entityType, implicitJoin, parent, forceJoinType); + var forceLeftJoin = comparisonWithNullableEntity && Walker.IsNullComparison; + DereferenceEntityJoin(classAlias, entityType, implicitJoin, parent, forceLeftJoin); if (comparisonWithNullableEntity) { _columns = FromElement.GetIdentityColumns(); @@ -458,7 +452,7 @@ private void DereferenceEntityIdentifier(string propertyName, DotNode dotParent) } } - private void DereferenceEntityJoin(string classAlias, EntityType propertyType, bool impliedJoin, IASTNode parent, bool forceJoinType) + private void DereferenceEntityJoin(string classAlias, EntityType propertyType, bool impliedJoin, IASTNode parent, bool forceLeftJoin) { _dereferenceType = DerefEntity; if ( Log.IsDebugEnabled() ) @@ -477,7 +471,11 @@ private void DereferenceEntityJoin(string classAlias, EntityType propertyType, b string[] joinColumns = GetColumns(); string joinPath = Path; - if (impliedJoin && !forceJoinType && Walker.IsInFrom) + if (forceLeftJoin) + { + _joinType = JoinType.LeftOuterJoin; + } + else if (impliedJoin && Walker.IsInFrom) { _joinType = Walker.ImpliedJoinType; } @@ -525,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(!forceJoinType && impliedJoin, propertyType, tableAlias, _joinType, joinColumns); + .CreateJoinSequence(!forceLeftJoin && impliedJoin, propertyType, tableAlias, _joinType, joinColumns); var factory = new FromElementFactory( currentFromClause, @@ -546,7 +544,7 @@ private void DereferenceEntityJoin(string classAlias, EntityType propertyType, b } else { - if (forceJoinType) + if (forceLeftJoin) { elem.JoinSequence.SetJoinType(_joinType); }