From 58b6c047c19652f502f7cf00e2e1666f2dad95eb Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Mon, 23 Nov 2020 13:37:55 +0200 Subject: [PATCH 1/3] Fix nullable entity comparison with null --- .../Async/Hql/EntityJoinHqlTest.cs | 104 +++++++++++++---- src/NHibernate.Test/Hql/EntityJoinHqlTest.cs | 105 ++++++++++++++---- .../Hql/EntityJoinHqlTestEntities.cs | 3 +- src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs | 14 ++- 4 files changed, 178 insertions(+), 48 deletions(-) diff --git a/src/NHibernate.Test/Async/Hql/EntityJoinHqlTest.cs b/src/NHibernate.Test/Async/Hql/EntityJoinHqlTest.cs index 0632a01771a..159213ad99f 100644 --- a/src/NHibernate.Test/Async/Hql/EntityJoinHqlTest.cs +++ b/src/NHibernate.Test/Async/Hql/EntityJoinHqlTest.cs @@ -8,11 +8,13 @@ //------------------------------------------------------------------------------ +using System.Linq; using System.Text.RegularExpressions; using NHibernate.Cfg.MappingSchema; using NHibernate.Mapping.ByCode; using NHibernate.Test.Hql.EntityJoinHqlTestEntities; using NUnit.Framework; +using NHibernate.Linq; namespace NHibernate.Test.Hql { @@ -164,12 +166,12 @@ public async Task EntityJoinWithNullableOneToOneEntityComparisonInWithClausShoul .CreateQuery( "select ex " + "from NullableOwner ex " - + "left join OneToOneEntity st with st = ex.OneToOne " - ).SetMaxResults(1) + + "inner join OneToOneEntity st with st = ex.OneToOne " + ) .UniqueResultAsync()); - Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "OneToOneEntity").Count, Is.EqualTo(2)); - Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + Assert.That(entity, Is.Not.Null); + Assert.That(Regex.Matches(sqlLog.Appender.GetEvents()[0].RenderedMessage, "OneToOneEntity").Count, Is.EqualTo(2)); } } @@ -188,8 +190,28 @@ public async Task NullableOneToOneWhereEntityIsNotNullAsync() ).SetMaxResults(1) .UniqueResultAsync()); - Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "OneToOneEntity").Count, Is.EqualTo(1)); - Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + Assert.That(entity, Is.Not.Null); + Assert.That(Regex.Matches(sqlLog.Appender.GetEvents()[0].RenderedMessage, "OneToOneEntity").Count, Is.EqualTo(1)); + } + } + + [Test] + public async Task NullableOneToOneWhereEntityIsNullAsync() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + var entity = + await (session + .CreateQuery( + "select ex " + + "from NullableOwner ex " + + "where ex.OneToOne is null " + ).SetMaxResults(1) + .UniqueResultAsync()); + + Assert.That(entity, Is.Not.Null); + Assert.That(Regex.Matches(sqlLog.Appender.GetEvents()[0].RenderedMessage, "OneToOneEntity").Count, Is.EqualTo(1)); } } @@ -208,8 +230,8 @@ public async Task NullableOneToOneWhereIdIsNotNullAsync() ).SetMaxResults(1) .UniqueResultAsync()); - Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "OneToOneEntity").Count, Is.EqualTo(1)); - Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + Assert.That(entity, Is.Not.Null); + Assert.That(Regex.Matches(sqlLog.Appender.GetEvents()[0].RenderedMessage, "OneToOneEntity").Count, Is.EqualTo(1)); } } @@ -222,14 +244,32 @@ public async Task NullablePropRefWhereIdEntityNotNullShouldAddJoinAsync() var entity = await (session .CreateQuery( - "select ex " - + "from NullableOwner ex " + "select ex from NullableOwner ex " + "where ex.PropRef is not null " - ).SetMaxResults(1) + ) .UniqueResultAsync()); - Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "PropRefEntity").Count, Is.EqualTo(1)); - Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + Assert.That(entity, Is.Not.Null); + Assert.That(Regex.Matches(sqlLog.Appender.GetEvents()[0].RenderedMessage, "PropRefEntity").Count, Is.EqualTo(1)); + } + } + + [Test] + public async Task NullablePropRefWhereIdEntityNullShouldAddJoinAsync() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + var entity = + await (session + .CreateQuery( + "select ex from NullableOwner ex " + + "where ex.PropRef is null " + ) + .UniqueResultAsync()); + + Assert.That(entity, Is.Not.Null); + Assert.That(Regex.Matches(sqlLog.Appender.GetEvents()[0].RenderedMessage, "PropRefEntity").Count, Is.EqualTo(1)); } } @@ -245,28 +285,41 @@ public async Task NullableOneToOneFetchQueryIsNotAffectedAsync() "select ex " + "from NullableOwner ex left join fetch ex.OneToOne o " + "where o is null " - ).SetMaxResults(1) + ) .UniqueResultAsync()); + Assert.That(entity, Is.Not.Null); Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "OneToOneEntity").Count, Is.EqualTo(1)); } } - + + [Test(Description = "GH-2611")] + public async Task NullableOneIsNullLinqAsync() + { + using (var session = OpenSession()) + { + var entity = await (session.Query().Where(x => x.OneToOne == null).FirstOrDefaultAsync()); + Assert.That(entity, Is.Not.Null); + } + } + [Test] public async Task NullableOneToOneFetchQueryIsNotAffected2Async() { using (var sqlLog = new SqlLogSpy()) using (var session = OpenSession()) { - var entity = + var entities = await (session .CreateQuery( "select ex " + "from NullableOwner ex left join fetch ex.OneToOne o " + "where o.Id is null " - ).SetMaxResults(1) - .UniqueResultAsync()); + ) + .ListAsync()); + var entity = entities[0]; + Assert.That(entity, Is.Not.Null); Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "OneToOneEntity").Count, Is.EqualTo(1)); } } @@ -488,8 +541,9 @@ protected override HbmMapping GetMappings() mapper.Class( rc => { - rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb)); + rc.Id(e => e.Id, m => m.Generator(Generators.Foreign(x => x.Parent))); rc.Property(e => e.Name); + rc.OneToOne(x => x.Parent, x => x.Constrained(true)); }); mapper.Class( @@ -505,7 +559,11 @@ protected override HbmMapping GetMappings() { rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb)); rc.Property(e => e.Name); - rc.OneToOne(e => e.OneToOne, m => m.Constrained(false)); + rc.OneToOne(e => e.OneToOne, m => + { + m.Constrained(false); + m.Cascade(Mapping.ByCode.Cascade.All); + }); rc.ManyToOne( e => e.PropRef, m => @@ -514,6 +572,7 @@ protected override HbmMapping GetMappings() m.PropertyRef(nameof(PropRefEntity.PropertyRef)); m.ForeignKey("none"); m.NotFound(NotFoundMode.Ignore); + m.Cascade(Mapping.ByCode.Cascade.All); }); }); @@ -584,7 +643,10 @@ protected override void OnSetUp() }; session.Save(_noAssociation); - session.Flush(); + session.Save(new NullableOwner() {Name = "NoAssociation"}); + var nullableOwner = new NullableOwner() {Name = "Association", PropRef = new PropRefEntity() {Name = "x", PropertyRef = "xx"}, OneToOne = new OneToOneEntity() {Name = "x"},}; + nullableOwner.OneToOne.Parent = nullableOwner; + session.Save(nullableOwner); transaction.Commit(); } } diff --git a/src/NHibernate.Test/Hql/EntityJoinHqlTest.cs b/src/NHibernate.Test/Hql/EntityJoinHqlTest.cs index f01b6303d30..f50bd0fb803 100644 --- a/src/NHibernate.Test/Hql/EntityJoinHqlTest.cs +++ b/src/NHibernate.Test/Hql/EntityJoinHqlTest.cs @@ -1,4 +1,5 @@ -using System.Text.RegularExpressions; +using System.Linq; +using System.Text.RegularExpressions; using NHibernate.Cfg.MappingSchema; using NHibernate.Mapping.ByCode; using NHibernate.Test.Hql.EntityJoinHqlTestEntities; @@ -153,12 +154,12 @@ public void EntityJoinWithNullableOneToOneEntityComparisonInWithClausShouldAddJo .CreateQuery( "select ex " + "from NullableOwner ex " - + "left join OneToOneEntity st with st = ex.OneToOne " - ).SetMaxResults(1) + + "inner join OneToOneEntity st with st = ex.OneToOne " + ) .UniqueResult(); - Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "OneToOneEntity").Count, Is.EqualTo(2)); - Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + Assert.That(entity, Is.Not.Null); + Assert.That(Regex.Matches(sqlLog.Appender.GetEvents()[0].RenderedMessage, "OneToOneEntity").Count, Is.EqualTo(2)); } } @@ -177,8 +178,28 @@ public void NullableOneToOneWhereEntityIsNotNull() ).SetMaxResults(1) .UniqueResult(); - Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "OneToOneEntity").Count, Is.EqualTo(1)); - Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + Assert.That(entity, Is.Not.Null); + Assert.That(Regex.Matches(sqlLog.Appender.GetEvents()[0].RenderedMessage, "OneToOneEntity").Count, Is.EqualTo(1)); + } + } + + [Test] + public void NullableOneToOneWhereEntityIsNull() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + var entity = + session + .CreateQuery( + "select ex " + + "from NullableOwner ex " + + "where ex.OneToOne is null " + ).SetMaxResults(1) + .UniqueResult(); + + Assert.That(entity, Is.Not.Null); + Assert.That(Regex.Matches(sqlLog.Appender.GetEvents()[0].RenderedMessage, "OneToOneEntity").Count, Is.EqualTo(1)); } } @@ -197,8 +218,8 @@ public void NullableOneToOneWhereIdIsNotNull() ).SetMaxResults(1) .UniqueResult(); - Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "OneToOneEntity").Count, Is.EqualTo(1)); - Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + Assert.That(entity, Is.Not.Null); + Assert.That(Regex.Matches(sqlLog.Appender.GetEvents()[0].RenderedMessage, "OneToOneEntity").Count, Is.EqualTo(1)); } } @@ -211,14 +232,32 @@ public void NullablePropRefWhereIdEntityNotNullShouldAddJoin() var entity = session .CreateQuery( - "select ex " - + "from NullableOwner ex " + "select ex from NullableOwner ex " + "where ex.PropRef is not null " - ).SetMaxResults(1) + ) .UniqueResult(); - Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "PropRefEntity").Count, Is.EqualTo(1)); - Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + Assert.That(entity, Is.Not.Null); + Assert.That(Regex.Matches(sqlLog.Appender.GetEvents()[0].RenderedMessage, "PropRefEntity").Count, Is.EqualTo(1)); + } + } + + [Test] + public void NullablePropRefWhereIdEntityNullShouldAddJoin() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + var entity = + session + .CreateQuery( + "select ex from NullableOwner ex " + + "where ex.PropRef is null " + ) + .UniqueResult(); + + Assert.That(entity, Is.Not.Null); + Assert.That(Regex.Matches(sqlLog.Appender.GetEvents()[0].RenderedMessage, "PropRefEntity").Count, Is.EqualTo(1)); } } @@ -234,28 +273,41 @@ public void NullableOneToOneFetchQueryIsNotAffected() "select ex " + "from NullableOwner ex left join fetch ex.OneToOne o " + "where o is null " - ).SetMaxResults(1) + ) .UniqueResult(); + Assert.That(entity, Is.Not.Null); Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "OneToOneEntity").Count, Is.EqualTo(1)); } } - + + [Test(Description = "GH-2611")] + public void NullableOneIsNullLinq() + { + using (var session = OpenSession()) + { + var entity = session.Query().Where(x => x.OneToOne == null).FirstOrDefault(); + Assert.That(entity, Is.Not.Null); + } + } + [Test] public void NullableOneToOneFetchQueryIsNotAffected2() { using (var sqlLog = new SqlLogSpy()) using (var session = OpenSession()) { - var entity = + var entities = session .CreateQuery( "select ex " + "from NullableOwner ex left join fetch ex.OneToOne o " + "where o.Id is null " - ).SetMaxResults(1) - .UniqueResult(); + ) + .List(); + var entity = entities[0]; + Assert.That(entity, Is.Not.Null); Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "OneToOneEntity").Count, Is.EqualTo(1)); } } @@ -493,8 +545,9 @@ protected override HbmMapping GetMappings() mapper.Class( rc => { - rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb)); + rc.Id(e => e.Id, m => m.Generator(Generators.Foreign(x => x.Parent))); rc.Property(e => e.Name); + rc.OneToOne(x => x.Parent, x => x.Constrained(true)); }); mapper.Class( @@ -510,7 +563,11 @@ protected override HbmMapping GetMappings() { rc.Id(e => e.Id, m => m.Generator(Generators.GuidComb)); rc.Property(e => e.Name); - rc.OneToOne(e => e.OneToOne, m => m.Constrained(false)); + rc.OneToOne(e => e.OneToOne, m => + { + m.Constrained(false); + m.Cascade(Mapping.ByCode.Cascade.All); + }); rc.ManyToOne( e => e.PropRef, m => @@ -519,6 +576,7 @@ protected override HbmMapping GetMappings() m.PropertyRef(nameof(PropRefEntity.PropertyRef)); m.ForeignKey("none"); m.NotFound(NotFoundMode.Ignore); + m.Cascade(Mapping.ByCode.Cascade.All); }); }); @@ -589,7 +647,10 @@ protected override void OnSetUp() }; session.Save(_noAssociation); - session.Flush(); + session.Save(new NullableOwner() {Name = "NoAssociation"}); + var nullableOwner = new NullableOwner() {Name = "Association", PropRef = new PropRefEntity() {Name = "x", PropertyRef = "xx"}, OneToOne = new OneToOneEntity() {Name = "x"},}; + nullableOwner.OneToOne.Parent = nullableOwner; + session.Save(nullableOwner); transaction.Commit(); } } diff --git a/src/NHibernate.Test/Hql/EntityJoinHqlTestEntities.cs b/src/NHibernate.Test/Hql/EntityJoinHqlTestEntities.cs index 277b0ea706b..5571b0decf4 100644 --- a/src/NHibernate.Test/Hql/EntityJoinHqlTestEntities.cs +++ b/src/NHibernate.Test/Hql/EntityJoinHqlTestEntities.cs @@ -16,8 +16,9 @@ public class OneToOneEntity { public virtual Guid Id { get; set; } public virtual string Name { get; set; } + public virtual NullableOwner Parent { get; set; } } - + public class PropRefEntity { public virtual Guid Id { get; set; } diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs index bbe0a91b740..08b61bf1c65 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs @@ -409,9 +409,15 @@ private void DereferenceEntity(EntityType entityType, bool implicitJoin, string } else { - comparisonWithNullableEntity = (Walker.IsComparativeExpressionClause && entityType.IsNullable); - joinIsNeeded = generateJoin || (Walker.IsInSelect && !Walker.IsInCase) || (Walker.IsInFrom && !Walker.IsComparativeExpressionClause) - || comparisonWithNullableEntity; + if (Walker.IsComparativeExpressionClause && entityType.IsNullable) + { + joinIsNeeded = comparisonWithNullableEntity = true; + _joinType = JoinType.LeftOuterJoin; + } + else + { + joinIsNeeded = generateJoin || (Walker.IsInSelect && !Walker.IsInCase) || (Walker.IsInFrom && !Walker.IsComparativeExpressionClause); + } } if ( joinIsNeeded ) @@ -517,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(_joinType != JoinType.LeftOuterJoin && impliedJoin, propertyType, tableAlias, _joinType, joinColumns); var factory = new FromElementFactory( currentFromClause, From 1dec74d736292e0e1b26e576177cc4787758488c Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Mon, 23 Nov 2020 18:33:09 +0200 Subject: [PATCH 2/3] Fix for queries with implicit joins --- .../Async/Hql/EntityJoinHqlTest.cs | 19 ++++++++++++++----- src/NHibernate.Test/Hql/EntityJoinHqlTest.cs | 18 ++++++++++++++---- src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs | 7 ++++++- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/NHibernate.Test/Async/Hql/EntityJoinHqlTest.cs b/src/NHibernate.Test/Async/Hql/EntityJoinHqlTest.cs index 159213ad99f..30755c9b1a4 100644 --- a/src/NHibernate.Test/Async/Hql/EntityJoinHqlTest.cs +++ b/src/NHibernate.Test/Async/Hql/EntityJoinHqlTest.cs @@ -11,10 +11,10 @@ using System.Linq; using System.Text.RegularExpressions; using NHibernate.Cfg.MappingSchema; +using NHibernate.Linq; using NHibernate.Mapping.ByCode; using NHibernate.Test.Hql.EntityJoinHqlTestEntities; using NUnit.Framework; -using NHibernate.Linq; namespace NHibernate.Test.Hql { @@ -298,7 +298,17 @@ public async Task NullableOneIsNullLinqAsync() { using (var session = OpenSession()) { - var entity = await (session.Query().Where(x => x.OneToOne == null).FirstOrDefaultAsync()); + var entity = await (session.Query().Where(x => x.OneToOne == null).SingleOrDefaultAsync()); + Assert.That(entity, Is.Not.Null); + } + } + + [Test(Description = "GH-2611")] + public async Task NullableOneFetchIsNullLinqAsync() + { + using (var session = OpenSession()) + { + var entity = await (session.Query().Fetch(x => x.OneToOne).Where(x => x.OneToOne == null).SingleOrDefaultAsync()); Assert.That(entity, Is.Not.Null); } } @@ -309,15 +319,14 @@ public async Task NullableOneToOneFetchQueryIsNotAffected2Async() using (var sqlLog = new SqlLogSpy()) using (var session = OpenSession()) { - var entities = + var entity = await (session .CreateQuery( "select ex " + "from NullableOwner ex left join fetch ex.OneToOne o " + "where o.Id is null " ) - .ListAsync()); - var entity = entities[0]; + .UniqueResultAsync()); Assert.That(entity, Is.Not.Null); Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "OneToOneEntity").Count, Is.EqualTo(1)); diff --git a/src/NHibernate.Test/Hql/EntityJoinHqlTest.cs b/src/NHibernate.Test/Hql/EntityJoinHqlTest.cs index f50bd0fb803..9dd6712b2df 100644 --- a/src/NHibernate.Test/Hql/EntityJoinHqlTest.cs +++ b/src/NHibernate.Test/Hql/EntityJoinHqlTest.cs @@ -1,6 +1,7 @@ using System.Linq; using System.Text.RegularExpressions; using NHibernate.Cfg.MappingSchema; +using NHibernate.Linq; using NHibernate.Mapping.ByCode; using NHibernate.Test.Hql.EntityJoinHqlTestEntities; using NUnit.Framework; @@ -286,7 +287,17 @@ public void NullableOneIsNullLinq() { using (var session = OpenSession()) { - var entity = session.Query().Where(x => x.OneToOne == null).FirstOrDefault(); + var entity = session.Query().Where(x => x.OneToOne == null).SingleOrDefault(); + Assert.That(entity, Is.Not.Null); + } + } + + [Test(Description = "GH-2611")] + public void NullableOneFetchIsNullLinq() + { + using (var session = OpenSession()) + { + var entity = session.Query().Fetch(x => x.OneToOne).Where(x => x.OneToOne == null).SingleOrDefault(); Assert.That(entity, Is.Not.Null); } } @@ -297,15 +308,14 @@ public void NullableOneToOneFetchQueryIsNotAffected2() using (var sqlLog = new SqlLogSpy()) using (var session = OpenSession()) { - var entities = + var entity = session .CreateQuery( "select ex " + "from NullableOwner ex left join fetch ex.OneToOne o " + "where o.Id is null " ) - .List(); - var entity = entities[0]; + .UniqueResult(); Assert.That(entity, Is.Not.Null); Assert.That(Regex.Matches(sqlLog.GetWholeLog(), "OneToOneEntity").Count, Is.EqualTo(1)); diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs index 08b61bf1c65..01d3239c61c 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using Antlr.Runtime; using NHibernate.Engine; @@ -412,7 +413,11 @@ private void DereferenceEntity(EntityType entityType, bool implicitJoin, string if (Walker.IsComparativeExpressionClause && entityType.IsNullable) { joinIsNeeded = comparisonWithNullableEntity = true; - _joinType = JoinType.LeftOuterJoin; + + //TODO: Fix this hack. We should always left join here. Skip left join for nullable entity if query contains implicit joins. We currently don't support such queries (see OneToOneCompositeQueryCompareWithJoin) + var fromJoins = ASTUtil.CollectChildren(Walker.CurrentFromClause, x => x is FromElement fe && !fe.IsImplied && fe.Type == HqlSqlWalker.FROM_FRAGMENT); + if(fromJoins.Count == 1) + _joinType = JoinType.LeftOuterJoin; } else { From 27ab1f13b014d45b67782c60d97700ca3370b5e7 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Mon, 23 Nov 2020 19:45:13 +0200 Subject: [PATCH 3/3] Harden OneToOneFixture --- .../Associations/OneToOneFixture.cs | 23 +++++++++++++++---- .../Async/Associations/OneToOneFixture.cs | 23 +++++++++++++++---- src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs | 2 +- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/NHibernate.Test/Associations/OneToOneFixture.cs b/src/NHibernate.Test/Associations/OneToOneFixture.cs index 8ea08d6a425..ee1096a585f 100644 --- a/src/NHibernate.Test/Associations/OneToOneFixture.cs +++ b/src/NHibernate.Test/Associations/OneToOneFixture.cs @@ -76,12 +76,12 @@ public void OneToOneCompositeQueryOverByNotNull() { using (var session = OpenSession()) { - var loadedEntity = session.QueryOver().Where(p => p.OneToOneComp != null).SingleOrDefault(); + var loadedEntity = session.QueryOver().Where(p => p.OneToOneComp != null).JoinQueryOver(x => x.OneToOneComp).SingleOrDefault(); Assert.That(loadedEntity, Is.Not.Null); } } - + [Test] public void OneToOneCompositeQueryCompareWithJoin() { @@ -92,7 +92,20 @@ public void OneToOneCompositeQueryCompareWithJoin() Assert.That(loadedEntity, Is.Not.Null); } } - + + [Explicit("Not supported.")] + [Test] + public void OneToOneCompositeQueryCompareWithJoinOrIsNull() + { + using(new SqlLogSpy()) + using (var session = OpenSession()) + { + var loadedEntities = session.CreateQuery("select p from Parent p, EntityWithCompositeId e where p.OneToOneComp = e or p.OneToOneComp is null").List(); + + Assert.That(loadedEntities.Count, Is.EqualTo(2)); + } + } + [Explicit("Expression in Restrictions.Where can't recognize direct alias comparison.")] [Test] public void OneToOneCompositeQueryOverCompareWithJoin() @@ -134,7 +147,7 @@ public void OneToOneCompositeQuerySelectProjection() { using (var session = OpenSession()) { - var loadedProjection = session.Query().Select(x => new {x.OneToOneComp, x.Key}).FirstOrDefault(); + var loadedProjection = session.Query().Where(x => x.OneToOneComp != null).Select(x => new {x.OneToOneComp, x.Key}).FirstOrDefault(); Assert.That(loadedProjection.OneToOneComp, Is.Not.Null); } @@ -147,6 +160,7 @@ public void OneToOneQueryOverSelectProjection() using (var session = OpenSession()) { var loadedEntity = session.QueryOver() + .JoinQueryOver(x => x.OneToOneComp) .Select(x => x.OneToOneComp) .SingleOrDefault(); @@ -225,6 +239,7 @@ protected override void OnSetUp() session.Save(oneToOneParent.OneToOneComp); session.Save(oneToOneParent); + session.Save(new Parent() {Key = new CompositeKey() {Id1 = 1, Id2 = 1}}); session.Flush(); transaction.Commit(); diff --git a/src/NHibernate.Test/Async/Associations/OneToOneFixture.cs b/src/NHibernate.Test/Async/Associations/OneToOneFixture.cs index a1bd0932d32..18f07b778e8 100644 --- a/src/NHibernate.Test/Async/Associations/OneToOneFixture.cs +++ b/src/NHibernate.Test/Async/Associations/OneToOneFixture.cs @@ -88,12 +88,12 @@ public async Task OneToOneCompositeQueryOverByNotNullAsync() { using (var session = OpenSession()) { - var loadedEntity = await (session.QueryOver().Where(p => p.OneToOneComp != null).SingleOrDefaultAsync()); + var loadedEntity = await (session.QueryOver().Where(p => p.OneToOneComp != null).JoinQueryOver(x => x.OneToOneComp).SingleOrDefaultAsync()); Assert.That(loadedEntity, Is.Not.Null); } } - + [Test] public async Task OneToOneCompositeQueryCompareWithJoinAsync() { @@ -104,7 +104,20 @@ public async Task OneToOneCompositeQueryCompareWithJoinAsync() Assert.That(loadedEntity, Is.Not.Null); } } - + + [Explicit("Not supported.")] + [Test] + public async Task OneToOneCompositeQueryCompareWithJoinOrIsNullAsync() + { + using(new SqlLogSpy()) + using (var session = OpenSession()) + { + var loadedEntities = await (session.CreateQuery("select p from Parent p, EntityWithCompositeId e where p.OneToOneComp = e or p.OneToOneComp is null").ListAsync()); + + Assert.That(loadedEntities.Count, Is.EqualTo(2)); + } + } + [Explicit("Expression in Restrictions.Where can't recognize direct alias comparison.")] [Test] public async Task OneToOneCompositeQueryOverCompareWithJoinAsync() @@ -146,7 +159,7 @@ public async Task OneToOneCompositeQuerySelectProjectionAsync() { using (var session = OpenSession()) { - var loadedProjection = await (session.Query().Select(x => new {x.OneToOneComp, x.Key}).FirstOrDefaultAsync()); + var loadedProjection = await (session.Query().Where(x => x.OneToOneComp != null).Select(x => new {x.OneToOneComp, x.Key}).FirstOrDefaultAsync()); Assert.That(loadedProjection.OneToOneComp, Is.Not.Null); } @@ -159,6 +172,7 @@ public async Task OneToOneQueryOverSelectProjectionAsync() using (var session = OpenSession()) { var loadedEntity = await (session.QueryOver() + .JoinQueryOver(x => x.OneToOneComp) .Select(x => x.OneToOneComp) .SingleOrDefaultAsync()); @@ -237,6 +251,7 @@ protected override void OnSetUp() session.Save(oneToOneParent.OneToOneComp); session.Save(oneToOneParent); + session.Save(new Parent() {Key = new CompositeKey() {Id1 = 1, Id2 = 1}}); session.Flush(); transaction.Commit(); diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs index 01d3239c61c..7daa43f84bb 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs @@ -414,7 +414,7 @@ private void DereferenceEntity(EntityType entityType, bool implicitJoin, string { joinIsNeeded = comparisonWithNullableEntity = true; - //TODO: Fix this hack. We should always left join here. Skip left join for nullable entity if query contains implicit joins. We currently don't support such queries (see OneToOneCompositeQueryCompareWithJoin) + //TODO: Fix this hack. Ideally we should always left join here. Skip left join for nullable entity if query contains implicit joins. We currently don't support left joins for such queries (see OneToOneCompositeQueryCompareWithJoinOrIsNull) var fromJoins = ASTUtil.CollectChildren(Walker.CurrentFromClause, x => x is FromElement fe && !fe.IsImplied && fe.Type == HqlSqlWalker.FROM_FRAGMENT); if(fromJoins.Count == 1) _joinType = JoinType.LeftOuterJoin;