Skip to content

Fix possible ERROR log when parsing join in hql #2859

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/NHibernate.Test/Associations/OneToOneFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<Parent>().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()
Expand Down
8 changes: 6 additions & 2 deletions src/NHibernate.Test/Async/Associations/OneToOneFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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<Parent>().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()
Expand Down
42 changes: 41 additions & 1 deletion src/NHibernate.Test/Async/Hql/EntityJoinHqlTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<EntityComplex>());

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<EntityComplex>());

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()
{
Expand Down
42 changes: 41 additions & 1 deletion src/NHibernate.Test/Hql/EntityJoinHqlTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<EntityComplex>();

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<EntityComplex>();

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()
{
Expand Down
12 changes: 11 additions & 1 deletion src/NHibernate.Test/Hql/EntityJoinHqlTestEntities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
44 changes: 20 additions & 24 deletions src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's either implicit or entity join. So skip any processing that's not fully resolve this node.

dot.Resolve( true, false, alias == null ? null : alias.Text );

FromElement fromElement;
Expand All @@ -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);

Expand All @@ -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(
Expand Down Expand Up @@ -790,42 +799,29 @@ 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)
{
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)
return persister;

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)
Expand Down
4 changes: 3 additions & 1 deletion src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ public string PropertyPath
set { _propertyPath = value; }
}

internal bool SkipSemiResolve { get; set; }

public override void SetScalarColumnText(int i)
{
string[] sqlColumns = GetColumns();
Expand Down Expand Up @@ -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 );
}
Expand Down