Skip to content

Fix missing join for reused fetch join not used in Select #3267

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 7 commits into from
Mar 24, 2023
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by AsyncGenerator.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------


using System.Linq;
using NHibernate.Linq;
using NUnit.Framework;

namespace NHibernate.Test.NHSpecificTest.GH3263
{
using System.Threading.Tasks;
[TestFixture]
public class ReuseFetchJoinFixtureAsync : BugTestCase
{
protected override void OnSetUp()
{
using var s = OpenSession();
using var t = s.BeginTransaction();
var em = new Employee() { Name = "x", OptionalInfo = new OptionalInfo() };
em.OptionalInfo.Employee = em;
s.Save(em);
t.Commit();
}
protected override void OnTearDown()
{
using var session = OpenSession();
using var transaction = session.BeginTransaction();
session.CreateQuery("delete from System.Object").ExecuteUpdate();

transaction.Commit();
}

[Test]
public async Task ReuseJoinScalarSelectAsync()
{
using var session = OpenSession();
await (session.Query<Employee>()
.Fetch(x => x.OptionalInfo)
.Where(x => x.OptionalInfo != null)
.Select(x => new { x.OptionalInfo.Age })
.ToListAsync());
}

[Test]
public async Task ReuseJoinScalarSelectHqlAsync()
{
using var session = OpenSession();
await (session.CreateQuery(
"select x.OptionalInfo.Age " +
"from Employee x " +
"fetch x.OptionalInfo " +
"where x.OptionalInfo != null ").ListAsync());

}

[Test]
public async Task ReuseJoinScalarSelectHql2Async()
{
using var session = OpenSession();
await (session.CreateQuery(
"select x.OptionalInfo.Age " +
"from Employee x " +
"join fetch x.OptionalInfo o " +
"where o != null ").ListAsync());
}

[Test]
public async Task ReuseJoinScalarSelectHql3Async()
{
using var session = OpenSession();
await (session.CreateQuery(
"select x.OptionalInfo.Age from Employee x " +
"join fetch x.OptionalInfo " +
"where x.OptionalInfo != null ").ListAsync());
}

[Test]
public async Task ReuseJoinEntityAndScalarSelectAsync()
{
using var session = OpenSession();
using var sqlLog = new SqlLogSpy();

var x = await (session.Query<Employee>()
.Fetch(x => x.OptionalInfo)
.Where(x => x.OptionalInfo != null)
.Select(x => new { x, x.OptionalInfo.Age })
.FirstAsync());

Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1));
Assert.That(NHibernateUtil.IsInitialized(x.x.OptionalInfo), Is.True);
}
}
}
16 changes: 16 additions & 0 deletions src/NHibernate.Test/NHSpecificTest/GH3263/Entity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace NHibernate.Test.NHSpecificTest.GH3263
{
public class Employee
{
public virtual int EmployeeId { get; set; }
public virtual string Name { get; set; }
public virtual OptionalInfo OptionalInfo { get; set; }
}

public class OptionalInfo
{
public virtual int EmployeeId { get; set; }
public virtual int Age { get; set; }
public virtual Employee Employee { get; set; }
}
}
25 changes: 25 additions & 0 deletions src/NHibernate.Test/NHSpecificTest/GH3263/Mappings.hbm.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernate.Test"
namespace="NHibernate.Test.NHSpecificTest.GH3263">
<class name="Employee" table="Employee">
<id name="EmployeeId">
<column name="EmployeeId" />
<generator class="identity" />
</id>
<property name="Name" >
<column name="Name" not-null="true" />
</property>
<one-to-one cascade="save-update" name="OptionalInfo" foreign-key="none"/>
</class>
<class name="OptionalInfo" table="OptionalInfo">
<id name="EmployeeId" >
<column name="EmployeeId" />
<generator class="foreign">
<param name="property">Employee</param>
</generator>
</id>
<property name="Age">
<column name="Age" not-null="true" />
</property>
<one-to-one cascade="none" name="Employee" foreign-key="none" />
</class>
</hibernate-mapping>
88 changes: 88 additions & 0 deletions src/NHibernate.Test/NHSpecificTest/GH3263/ReuseFetchJoinFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using System.Linq;
using NHibernate.Linq;
using NUnit.Framework;

namespace NHibernate.Test.NHSpecificTest.GH3263
{
[TestFixture]
public class ReuseFetchJoinFixture : BugTestCase
{
protected override void OnSetUp()
{
using var s = OpenSession();
using var t = s.BeginTransaction();
var em = new Employee() { Name = "x", OptionalInfo = new OptionalInfo() };
em.OptionalInfo.Employee = em;
s.Save(em);
t.Commit();
}
protected override void OnTearDown()
{
using var session = OpenSession();
using var transaction = session.BeginTransaction();
session.CreateQuery("delete from System.Object").ExecuteUpdate();

transaction.Commit();
}

[Test]
public void ReuseJoinScalarSelect()
{
using var session = OpenSession();
session.Query<Employee>()
.Fetch(x => x.OptionalInfo)
.Where(x => x.OptionalInfo != null)
.Select(x => new { x.OptionalInfo.Age })
.ToList();
}

[Test]
public void ReuseJoinScalarSelectHql()
{
using var session = OpenSession();
session.CreateQuery(
"select x.OptionalInfo.Age " +
"from Employee x " +
"fetch x.OptionalInfo " +
"where x.OptionalInfo != null ").List();

}

[Test]
public void ReuseJoinScalarSelectHql2()
{
using var session = OpenSession();
session.CreateQuery(
"select x.OptionalInfo.Age " +
"from Employee x " +
"join fetch x.OptionalInfo o " +
"where o != null ").List();
}

[Test]
public void ReuseJoinScalarSelectHql3()
{
using var session = OpenSession();
session.CreateQuery(
"select x.OptionalInfo.Age from Employee x " +
"join fetch x.OptionalInfo " +
"where x.OptionalInfo != null ").List();
}

[Test]
public void ReuseJoinEntityAndScalarSelect()
{
using var session = OpenSession();
using var sqlLog = new SqlLogSpy();

var x = session.Query<Employee>()
.Fetch(x => x.OptionalInfo)
.Where(x => x.OptionalInfo != null)
.Select(x => new { x, x.OptionalInfo.Age })
.First();

Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1));
Assert.That(NHibernateUtil.IsInitialized(x.x.OptionalInfo), Is.True);
}
}
}
2 changes: 2 additions & 0 deletions src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,8 @@ private void DereferenceEntityJoin(string classAlias, EntityType propertyType, b
{
elem.JoinSequence.SetJoinType(_joinType);
}

elem.ReusedJoin = true;
currentFromClause.AddDuplicateAlias(classAlias, elem);
}

Expand Down
1 change: 1 addition & 0 deletions src/NHibernate/Hql/Ast/ANTLR/Tree/FromElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,7 @@ internal virtual string[] GetIdentityColumns(string alias)
}

internal bool UseTableAliases => Walker.StatementType == HqlSqlWalker.SELECT || Walker.IsSubQuery;
internal bool ReusedJoin { get; set; }

public void HandlePropertyBeingDereferenced(IType propertySource, string propertyName)
{
Expand Down
2 changes: 1 addition & 1 deletion src/NHibernate/Hql/Ast/ANTLR/Tree/SelectClause.cs
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ private List<FromElement> GetFetchedFromElements(FromClause fromClause)
// throw new QueryException(string.Format(JoinFetchWithoutOwnerExceptionMsg, fromElement.GetDisplayText()));

//throw away the fromElement. It's clearly redundant.
if (fromElement.FromClause == fromClause)
if (fromElement.FromClause == fromClause && !fromElement.ReusedJoin)
{
fromElement.Parent.RemoveChild(fromElement);
}
Expand Down