From b3a260246fd4c675860e701216cbdb941f1c2914 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Tue, 13 Jul 2021 16:03:06 +0300 Subject: [PATCH] Fix possible ERROR log when parsing join in hql --- .../Associations/OneToOneFixture.cs | 8 +++- .../Async/Associations/OneToOneFixture.cs | 8 +++- .../Async/Hql/EntityJoinHqlTest.cs | 42 +++++++++++++++++- src/NHibernate.Test/Hql/EntityJoinHqlTest.cs | 42 +++++++++++++++++- .../Hql/EntityJoinHqlTestEntities.cs | 12 ++++- src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs | 44 +++++++++---------- src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs | 4 +- 7 files changed, 128 insertions(+), 32 deletions(-) diff --git a/src/NHibernate.Test/Associations/OneToOneFixture.cs b/src/NHibernate.Test/Associations/OneToOneFixture.cs index 8ea08d6a425..c9b6b880f49 100644 --- a/src/NHibernate.Test/Associations/OneToOneFixture.cs +++ b/src/NHibernate.Test/Associations/OneToOneFixture.cs @@ -3,6 +3,7 @@ using NHibernate.Cfg.MappingSchema; using NHibernate.Mapping.ByCode; using NHibernate.Test.Associations.OneToOneFixtureEntities; +using NHibernate.Util; using NUnit.Framework; namespace NHibernate.Test.Associations @@ -127,19 +128,22 @@ public void OneToOneCompositeQueryOverCompareWithJoinById() Assert.That(loadedEntity, Is.Not.Null); } } - + //GH-2064 [Test] public void OneToOneCompositeQuerySelectProjection() { + using(var logSpy = new LogSpy(typeof(ReflectHelper))) using (var session = OpenSession()) { var loadedProjection = session.Query().Select(x => new {x.OneToOneComp, x.Key}).FirstOrDefault(); Assert.That(loadedProjection.OneToOneComp, Is.Not.Null); + // GH-2855 Error is logged + Assert.That(logSpy.GetWholeLog(), Is.Empty); } } - + //NH-3178 (GH-1125) [Test] public void OneToOneQueryOverSelectProjection() diff --git a/src/NHibernate.Test/Async/Associations/OneToOneFixture.cs b/src/NHibernate.Test/Async/Associations/OneToOneFixture.cs index a1bd0932d32..c42d1baf67c 100644 --- a/src/NHibernate.Test/Async/Associations/OneToOneFixture.cs +++ b/src/NHibernate.Test/Async/Associations/OneToOneFixture.cs @@ -13,6 +13,7 @@ using NHibernate.Cfg.MappingSchema; using NHibernate.Mapping.ByCode; using NHibernate.Test.Associations.OneToOneFixtureEntities; +using NHibernate.Util; using NUnit.Framework; using NHibernate.Linq; @@ -139,19 +140,22 @@ public async Task OneToOneCompositeQueryOverCompareWithJoinByIdAsync() Assert.That(loadedEntity, Is.Not.Null); } } - + //GH-2064 [Test] public async Task OneToOneCompositeQuerySelectProjectionAsync() { + using(var logSpy = new LogSpy(typeof(ReflectHelper))) using (var session = OpenSession()) { var loadedProjection = await (session.Query().Select(x => new {x.OneToOneComp, x.Key}).FirstOrDefaultAsync()); Assert.That(loadedProjection.OneToOneComp, Is.Not.Null); + // GH-2855 Error is logged + Assert.That(logSpy.GetWholeLog(), Is.Empty); } } - + //NH-3178 (GH-1125) [Test] public async Task OneToOneQueryOverSelectProjectionAsync() diff --git a/src/NHibernate.Test/Async/Hql/EntityJoinHqlTest.cs b/src/NHibernate.Test/Async/Hql/EntityJoinHqlTest.cs index 10def317de4..f016139f79b 100644 --- a/src/NHibernate.Test/Async/Hql/EntityJoinHqlTest.cs +++ b/src/NHibernate.Test/Async/Hql/EntityJoinHqlTest.cs @@ -26,7 +26,7 @@ namespace NHibernate.Test.Hql [TestFixture] public class EntityJoinHqlTestAsync : TestCaseMappingByCode { - private const string _customEntityName = "CustomEntityName"; + private const string _customEntityName = "CustomEntityName.Test"; private EntityWithCompositeId _entityWithCompositeId; private EntityWithNoAssociation _noAssociation; private EntityCustomEntityName _entityWithCustomEntityName; @@ -51,6 +51,46 @@ public async Task CanJoinNotAssociatedEntityAsync() } } + [Test] + public async Task CanJoinNotAssociatedEntityFullNameAsync() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityComplex entityComplex = + await (session + .CreateQuery("select ex " + + "from EntityWithNoAssociation root " + + $"left join {typeof(EntityComplex).FullName} ex with root.Complex1Id = ex.Id") + .SetMaxResults(1) + .UniqueResultAsync()); + + Assert.That(entityComplex, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(entityComplex), Is.True); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + [Test] + public async Task CanJoinNotAssociatedInterfaceFullNameAsync() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityComplex entityComplex = + await (session + .CreateQuery("select ex " + + "from EntityWithNoAssociation root " + + $"left join {typeof(IEntityComplex).FullName} ex with root.Complex1Id = ex.Id") + .SetMaxResults(1) + .UniqueResultAsync()); + + Assert.That(entityComplex, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(entityComplex), Is.True); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + [Test] public async Task CanJoinNotAssociatedEntity_OnKeywordAsync() { diff --git a/src/NHibernate.Test/Hql/EntityJoinHqlTest.cs b/src/NHibernate.Test/Hql/EntityJoinHqlTest.cs index ecc64c5a5c4..2eec6634d65 100644 --- a/src/NHibernate.Test/Hql/EntityJoinHqlTest.cs +++ b/src/NHibernate.Test/Hql/EntityJoinHqlTest.cs @@ -14,7 +14,7 @@ namespace NHibernate.Test.Hql [TestFixture] public class EntityJoinHqlTest : TestCaseMappingByCode { - private const string _customEntityName = "CustomEntityName"; + private const string _customEntityName = "CustomEntityName.Test"; private EntityWithCompositeId _entityWithCompositeId; private EntityWithNoAssociation _noAssociation; private EntityCustomEntityName _entityWithCustomEntityName; @@ -39,6 +39,46 @@ public void CanJoinNotAssociatedEntity() } } + [Test] + public void CanJoinNotAssociatedEntityFullName() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityComplex entityComplex = + session + .CreateQuery("select ex " + + "from EntityWithNoAssociation root " + + $"left join {typeof(EntityComplex).FullName} ex with root.Complex1Id = ex.Id") + .SetMaxResults(1) + .UniqueResult(); + + Assert.That(entityComplex, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(entityComplex), Is.True); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + + [Test] + public void CanJoinNotAssociatedInterfaceFullName() + { + using (var sqlLog = new SqlLogSpy()) + using (var session = OpenSession()) + { + EntityComplex entityComplex = + session + .CreateQuery("select ex " + + "from EntityWithNoAssociation root " + + $"left join {typeof(IEntityComplex).FullName} ex with root.Complex1Id = ex.Id") + .SetMaxResults(1) + .UniqueResult(); + + Assert.That(entityComplex, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(entityComplex), Is.True); + Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected"); + } + } + [Test] public void CanJoinNotAssociatedEntity_OnKeyword() { diff --git a/src/NHibernate.Test/Hql/EntityJoinHqlTestEntities.cs b/src/NHibernate.Test/Hql/EntityJoinHqlTestEntities.cs index 4535bd27886..59fbd93699b 100644 --- a/src/NHibernate.Test/Hql/EntityJoinHqlTestEntities.cs +++ b/src/NHibernate.Test/Hql/EntityJoinHqlTestEntities.cs @@ -2,7 +2,17 @@ namespace NHibernate.Test.Hql.EntityJoinHqlTestEntities { - public class EntityComplex + public interface IEntityComplex + { + Guid Id { get; set; } + int Version { get; set; } + string Name { get; set; } + string LazyProp { get; set; } + EntityComplex SameTypeChild { get; set; } + EntityComplex SameTypeChild2 { get; set; } + } + + public class EntityComplex : IEntityComplex { public virtual Guid Id { get; set; } public virtual int Version { get; set; } diff --git a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs index 877286c2470..dd3fa852c75 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs @@ -702,12 +702,10 @@ void CreateFromJoinElement( // 2) an entity-join (join com.acme.User) // // so make the proper interpretation here... - var entityJoinReferencedPersister = ResolveEntityJoinReferencedPersister(path); - if (entityJoinReferencedPersister != null) + // DOT node processing was moved to prefer implicit join path before probing for entity join + if (path.Type == IDENT) { - var entityJoin = CreateEntityJoin(entityJoinReferencedPersister, alias, joinType, with); - ((FromReferenceNode) path).FromElement = entityJoin; - SetPropertyFetch(entityJoin, propertyFetch, alias); + ProcessAsEntityJoin(); return; } // The path AST should be a DotNode, and it should have been evaluated already. @@ -725,6 +723,7 @@ void CreateFromJoinElement( // Generate an explicit join for the root dot node. The implied joins will be collected and passed up // to the root dot node. + dot.SkipSemiResolve = true; dot.Resolve( true, false, alias == null ? null : alias.Text ); FromElement fromElement; @@ -738,7 +737,8 @@ void CreateFromJoinElement( fromElement = dot.GetImpliedJoin(); if (fromElement == null) { - throw new InvalidPathException("Invalid join: " + dot.Path); + ProcessAsEntityJoin(); + return; } SetPropertyFetch(fromElement, propertyFetch, alias); @@ -762,6 +762,15 @@ void CreateFromJoinElement( { log.Debug("createFromJoinElement() : {0}", _printer.ShowAsString( fromElement, "-- join tree --" )); } + + void ProcessAsEntityJoin() + { + var node = (FromReferenceNode) path; + var entityJoinReferencedPersister = ResolveEntityJoinReferencedPersister(node); + var entityJoin = CreateEntityJoin(entityJoinReferencedPersister, alias, joinType, with); + node.FromElement = entityJoin; + SetPropertyFetch(entityJoin, propertyFetch, alias); + } } private EntityJoinFromElement CreateEntityJoin( @@ -790,9 +799,9 @@ private EntityJoinFromElement CreateEntityJoin( return join; } - private IQueryable ResolveEntityJoinReferencedPersister(IASTNode path) + private IQueryable ResolveEntityJoinReferencedPersister(FromReferenceNode path) { - string entityName = GetEntityJoinCandidateEntityName(path); + string entityName = path.Path; var persister = SessionFactoryHelper.FindQueryableUsingImports(entityName); if (persister == null && entityName != null) @@ -800,7 +809,7 @@ private IQueryable ResolveEntityJoinReferencedPersister(IASTNode path) var implementors = SessionFactoryHelper.Factory.GetImplementors(entityName); //Possible case - join on interface if (implementors.Length == 1) - persister = SessionFactoryHelper.FindQueryableUsingImports(implementors[0]); + persister = (IQueryable) SessionFactoryHelper.Factory.TryGetEntityPersister(implementors[0]); } if (persister != null) @@ -808,24 +817,11 @@ private IQueryable ResolveEntityJoinReferencedPersister(IASTNode path) if (path.Type == IDENT) { - // Since IDENT node is not expected for implicit join path, we can throw on not found persister throw new QuerySyntaxException(entityName + " is not mapped"); } - return null; - } - - private static string GetEntityJoinCandidateEntityName(IASTNode path) - { - switch (path.Type) - { - case IDENT: - return ((IdentNode) path).Path; - case DOT: - return ASTUtil.GetPathText(path); - } - - return null; + //Keep old exception for DOT node + throw new InvalidPathException("Invalid join: " + entityName); } private static string GetPropertyPath(DotNode dotNode, IASTNode alias) diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs index ab45ae4feeb..9db7baacb2f 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs @@ -124,6 +124,8 @@ public string PropertyPath set { _propertyPath = value; } } + internal bool SkipSemiResolve { get; set; } + public override void SetScalarColumnText(int i) { string[] sqlColumns = GetColumns(); @@ -200,7 +202,7 @@ public override void Resolve(bool generateJoin, bool implicitJoin, string classA // this might be a Java constant. if ( propertyType == null ) { - if ( parent == null ) + if (parent == null && !SkipSemiResolve) { Walker.LiteralProcessor.LookupConstant( this ); }