Skip to content

Fix missing joins in subquery in case joins are present in main query #2494

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 3 commits into from
Aug 27, 2020
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
64 changes: 64 additions & 0 deletions src/NHibernate.Test/Async/NHSpecificTest/NH3622/FixtureByCode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//------------------------------------------------------------------------------
// <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;
using System.Linq;
using System.Text.RegularExpressions;
using NHibernate.Cfg.MappingSchema;
using NHibernate.Mapping.ByCode;
using NUnit.Framework;
using NHibernate.Linq;

namespace NHibernate.Test.NHSpecificTest.NH3622
{
using System.Threading.Tasks;
/// <summary>
/// Fixture using 'by code' mappings
/// </summary>
[TestFixture]
public class ByCodeFixtureAsync : TestCaseMappingByCode
{
protected override HbmMapping GetMappings()
{
var mapper = new ModelMapper();
mapper.AddMapping<TagMap>();
mapper.AddMapping<EquipmentMap>();
mapper.AddMapping<DisciplineMap>();
mapper.AddMapping<DisciplineTypeMap>();
return mapper.CompileMappingForAllExplicitlyAddedEntities();
}

[Test]
public async Task MissingJoinsInSubqueryAsync()
{
var id = Guid.NewGuid();
using( var logSpy = new SqlLogSpy())
using (var s = OpenSession())
{
var x = s.Query<Tag>()
.Where(tag => tag.Equipment.Discipline.DisciplineType.Id == id)
.Select(tag => tag.Id);

var y = await (s.Query<Tag>()
.Where(tag => x.Contains(tag.Id))
.Fetch(tag => tag.Equipment)
.ThenFetch(equipment => equipment.Discipline)
.ThenFetch(discipline => discipline.DisciplineType)
.ToListAsync());

var sql = logSpy.GetWholeLog();
var findSubqyeryIndex = sql.IndexOf(" in (");
var capturesCount = Regex.Matches(sql.Substring(findSubqyeryIndex), "inner join").Count;
//Expected joins for tag.Equipment.Discipline in subquery
Assert.That(capturesCount, Is.EqualTo(2), "Missing inner joins in subquery: " + sql);
}
}
}
}
90 changes: 90 additions & 0 deletions src/NHibernate.Test/NHSpecificTest/NH3622/Entity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System;
using NHibernate.Mapping.ByCode;
using NHibernate.Mapping.ByCode.Conformist;

namespace NHibernate.Test.NHSpecificTest.NH3622
{
public class Tag
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual Equipment Equipment { get; set; }
}

public class Equipment
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual Discipline Discipline { get; set; }
}

public class Discipline
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual DisciplineType DisciplineType { get; set; }
}

public class DisciplineType
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
}

public class TagMap : ClassMapping<Tag>
{
public TagMap()
{
this.Id(x => x.Id, mapper => mapper.Generator(Generators.Assigned));
this.Property(x => x.Name, mapper => mapper.NotNullable(true));
this.ManyToOne(
x => x.Equipment,
mapper =>
{
mapper.NotNullable(true);
mapper.Column("EquipmentId");
});
}
}

public class EquipmentMap : ClassMapping<Equipment>
{
public EquipmentMap()
{
this.Id(x => x.Id, mapper => mapper.Generator(Generators.Assigned));
this.Property(x => x.Name, mapper => mapper.NotNullable(true));
this.ManyToOne(
x => x.Discipline,
mapper =>
{
mapper.Column("DisciplineId");
mapper.NotNullable(false);
});
}
}

public class DisciplineMap : ClassMapping<Discipline>
{
public DisciplineMap()
{
this.Id(x => x.Id, mapper => mapper.Generator(Generators.Assigned));
this.Property(x => x.Name, mapper => mapper.NotNullable(true));
this.ManyToOne(
x => x.DisciplineType,
mapper =>
{
mapper.Column("DisciplineTypeId");
mapper.NotNullable(false);
});
}
}

public class DisciplineTypeMap : ClassMapping<DisciplineType>
{
public DisciplineTypeMap()
{
this.Id(x => x.Id, mapper => mapper.Generator(Generators.Assigned));
this.Property(x => x.Name, mapper => mapper.NotNullable(true));
}
}
}
53 changes: 53 additions & 0 deletions src/NHibernate.Test/NHSpecificTest/NH3622/FixtureByCode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using System.Linq;
using System.Text.RegularExpressions;
using NHibernate.Cfg.MappingSchema;
using NHibernate.Mapping.ByCode;
using NUnit.Framework;
using NHibernate.Linq;

namespace NHibernate.Test.NHSpecificTest.NH3622
{
/// <summary>
/// Fixture using 'by code' mappings
/// </summary>
[TestFixture]
public class ByCodeFixture : TestCaseMappingByCode
{
protected override HbmMapping GetMappings()
{
var mapper = new ModelMapper();
mapper.AddMapping<TagMap>();
mapper.AddMapping<EquipmentMap>();
mapper.AddMapping<DisciplineMap>();
mapper.AddMapping<DisciplineTypeMap>();
return mapper.CompileMappingForAllExplicitlyAddedEntities();
}

[Test]
public void MissingJoinsInSubquery()
{
var id = Guid.NewGuid();
using( var logSpy = new SqlLogSpy())
using (var s = OpenSession())
{
var x = s.Query<Tag>()
.Where(tag => tag.Equipment.Discipline.DisciplineType.Id == id)
.Select(tag => tag.Id);

var y = s.Query<Tag>()
.Where(tag => x.Contains(tag.Id))
.Fetch(tag => tag.Equipment)
.ThenFetch(equipment => equipment.Discipline)
.ThenFetch(discipline => discipline.DisciplineType)
.ToList();

var sql = logSpy.GetWholeLog();
var findSubqyeryIndex = sql.IndexOf(" in (");
var capturesCount = Regex.Matches(sql.Substring(findSubqyeryIndex), "inner join").Count;
//Expected joins for tag.Equipment.Discipline in subquery
Assert.That(capturesCount, Is.EqualTo(2), "Missing inner joins in subquery: " + sql);
}
}
}
}
22 changes: 15 additions & 7 deletions src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -504,13 +504,8 @@ private void DereferenceEntityJoin(string classAlias, EntityType propertyType, b
//
///////////////////////////////////////////////////////////////////////////////

bool found = elem != null;
// even though we might find a pre-existing element by join path, for FromElements originating in a from-clause
// we should only ever use the found element if the aliases match (null != null here).
// Implied joins are ok to reuse only if in same from clause (are there any other cases when we should reject implied joins?).
bool useFoundFromElement = found &&
(elem.IsImplied && elem.FromClause == currentFromClause || // NH different behavior (NH-3002)
AreSame(classAlias, elem.ClassAlias));
// even though we might find a pre-existing element by join path, we may not be able to reuse it...
bool useFoundFromElement = elem != null && CanReuse(classAlias, elem);

if ( ! useFoundFromElement )
{
Expand Down Expand Up @@ -551,6 +546,19 @@ private bool AreSame(String alias1, String alias2) {
return !StringHelper.IsEmpty( alias1 ) && !StringHelper.IsEmpty( alias2 ) && alias1.Equals( alias2 );
}

private bool CanReuse(string classAlias, FromElement fromElement)
{
// if the from-clauses are the same, we can be a little more aggressive in terms of what we reuse
if (fromElement.FromClause == Walker.CurrentFromClause &&
AreSame(classAlias, fromElement.ClassAlias))
{
return true;
}

// otherwise (subquery case) don't reuse the fromElement if we are processing the from-clause of the subquery
return Walker.CurrentClauseType != HqlSqlWalker.FROM;
}

private void SetImpliedJoin(FromElement elem)
{
_impliedJoin = elem;
Expand Down