Skip to content

Use inner join instead of implicit join for implied entity joins #2990

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 11 commits into from
Jul 21, 2022
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
2 changes: 1 addition & 1 deletion .github/workflows/NetCoreTests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- DB: Firebird
CONNECTION_STRING: "DataSource=localhost;Database=nhibernate;User=SYSDBA;Password=nhibernate;charset=utf8;"
- DB: MySQL
CONNECTION_STRING: "Server=localhost;Uid=root;Password=nhibernate;Database=nhibernate;Old Guids=True;"
CONNECTION_STRING: "Server=localhost;Uid=root;Password=nhibernate;Database=nhibernate;Old Guids=True;SslMode=none;"
- DB: Oracle
CONNECTION_STRING: "User ID=nhibernate;Password=nhibernate;Metadata Pooling=false;Self Tuning=false;Data Source=(DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 1521)) (CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME = XEPDB1)))"
- DB: SQLite
Expand Down
1 change: 1 addition & 0 deletions build-common/NHibernate.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<VersionPatch Condition="'$(VersionPatch)' == ''">0</VersionPatch>
<!-- Clear VersionSuffix for making release and set it to dev for making development builds -->
<VersionSuffix Condition="'$(VersionSuffix)' == ''">dev</VersionSuffix>
<LangVersion Condition="'$(MSBuildProjectExtension)' != '.vbproj'">9.0</LangVersion>

<VersionPrefix Condition="'$(VersionPrefix)' == ''">$(NhVersion).$(VersionPatch)</VersionPrefix>
<VersionSuffix Condition="'$(VersionSuffix)' != '' AND '$(BuildNumber)' != ''">$(VersionSuffix).$(BuildNumber)</VersionSuffix>
Expand Down
7 changes: 7 additions & 0 deletions src/NHibernate.DomainModel/Container.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ public Glarch Glarch
private IDictionary<string, Ternary> _ternaryMap;
//<set> mapping
private ISet<Ternary> _ternarySet;
private Simple _manyToOne;

public virtual IList<Simple> OneToMany
{
Expand Down Expand Up @@ -129,6 +130,12 @@ public virtual long Id
set { _id = value; }
}

public virtual Simple ManyToOne
{
get => _manyToOne;
set => _manyToOne = value;
}

public virtual IList<Contained> Bag
{
get { return _bag; }
Expand Down
2 changes: 1 addition & 1 deletion src/NHibernate.DomainModel/Container.hbm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
>
<generator class="native" />
</id>

<many-to-one name="ManyToOne" lazy="proxy" />
<list
name="OneToMany"
lazy="true"
Expand Down
11 changes: 8 additions & 3 deletions src/NHibernate.Test/Async/Legacy/ParentChildTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -359,8 +359,8 @@ public async Task CollectionQueryAsync()
if (!TestDialect.SupportsEmptyInsertsOrHasNonIdentityNativeGenerator)
Assert.Ignore("Support of empty inserts is required");

ISession s = OpenSession();
ITransaction t = s.BeginTransaction();
using var s = OpenSession();
using var t = s.BeginTransaction();

Simple s1 = new Simple();
s1.Name = "s";
Expand All @@ -383,10 +383,15 @@ public async Task CollectionQueryAsync()
l.Add(null);
l.Add(s2);
c.ManyToMany = l;
c.ManyToOne = new Simple { Name = "x", Count = 4};
await (s.SaveAsync(c.ManyToOne, c.ManyToOne.Count));
await (s.SaveAsync(c));

Assert.AreEqual(1,
(await (s.CreateQuery("select c from c in class ContainerX, s in class Simple where c.OneToMany[2] = s").ListAsync
())).Count);
Assert.AreEqual(1,
(await (s.CreateQuery("select c from c in class ContainerX, s in class Simple where c.OneToMany[2] = s and c.ManyToOne.Name = 'x'").ListAsync
())).Count);
Assert.AreEqual(1,
(await (s.CreateQuery("select c from c in class ContainerX, s in class Simple where c.ManyToMany[2] = s").
Expand Down Expand Up @@ -424,13 +429,13 @@ public async Task CollectionQueryAsync()
"select c from c in class ContainerX where c.ManyToMany[ c.OneToMany[0].Count ].Name = 's'").ListAsync())).
Count);

await (s.DeleteAsync(c.ManyToOne));
await (s.DeleteAsync(c));
await (s.DeleteAsync(s1));
await (s.DeleteAsync(s2));
await (s.DeleteAsync(s3));

await (t.CommitAsync());
s.Close();
}

[Test]
Expand Down
6 changes: 3 additions & 3 deletions src/NHibernate.Test/Async/NHSpecificTest/NH1444/Fixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ public async Task BugAsync()
var message = ls.GetWholeLog();
var paramFormatter = (ISqlParameterFormatter)Sfi.ConnectionProvider.Driver;
Assert.That(message, Does.Contain(
"xchild0_.ParentId=xparent1_.Id and (" +
$"{paramFormatter.GetParameterName(0)}={Dialect.ToBooleanValueString(true)} or " +
$"xparent1_.A<{paramFormatter.GetParameterName(1)})"));
"on xchild0_.ParentId=xparent1_.Id").And.Contain(
$"where {paramFormatter.GetParameterName(0)}={Dialect.ToBooleanValueString(true)} or " +
$"xparent1_.A<{paramFormatter.GetParameterName(1)};"));
}
}
}
Expand Down
11 changes: 8 additions & 3 deletions src/NHibernate.Test/Legacy/ParentChildTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -348,8 +348,8 @@ public void CollectionQuery()
if (!TestDialect.SupportsEmptyInsertsOrHasNonIdentityNativeGenerator)
Assert.Ignore("Support of empty inserts is required");

ISession s = OpenSession();
ITransaction t = s.BeginTransaction();
using var s = OpenSession();
using var t = s.BeginTransaction();

Simple s1 = new Simple();
s1.Name = "s";
Expand All @@ -372,10 +372,15 @@ public void CollectionQuery()
l.Add(null);
l.Add(s2);
c.ManyToMany = l;
c.ManyToOne = new Simple { Name = "x", Count = 4};
s.Save(c.ManyToOne, c.ManyToOne.Count);
s.Save(c);

Assert.AreEqual(1,
s.CreateQuery("select c from c in class ContainerX, s in class Simple where c.OneToMany[2] = s").List
().Count);
Assert.AreEqual(1,
s.CreateQuery("select c from c in class ContainerX, s in class Simple where c.OneToMany[2] = s and c.ManyToOne.Name = 'x'").List
().Count);
Assert.AreEqual(1,
s.CreateQuery("select c from c in class ContainerX, s in class Simple where c.ManyToMany[2] = s").
Expand Down Expand Up @@ -413,13 +418,13 @@ public void CollectionQuery()
"select c from c in class ContainerX where c.ManyToMany[ c.OneToMany[0].Count ].Name = 's'").List().
Count);

s.Delete(c.ManyToOne);
s.Delete(c);
s.Delete(s1);
s.Delete(s2);
s.Delete(s3);

t.Commit();
s.Close();
}

[Test]
Expand Down
6 changes: 3 additions & 3 deletions src/NHibernate.Test/NHSpecificTest/NH1444/Fixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ public void Bug()
var message = ls.GetWholeLog();
var paramFormatter = (ISqlParameterFormatter)Sfi.ConnectionProvider.Driver;
Assert.That(message, Does.Contain(
"xchild0_.ParentId=xparent1_.Id and (" +
$"{paramFormatter.GetParameterName(0)}={Dialect.ToBooleanValueString(true)} or " +
$"xparent1_.A<{paramFormatter.GetParameterName(1)})"));
"on xchild0_.ParentId=xparent1_.Id").And.Contain(
$"where {paramFormatter.GetParameterName(0)}={Dialect.ToBooleanValueString(true)} or " +
$"xparent1_.A<{paramFormatter.GetParameterName(1)};"));
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -782,7 +782,12 @@ void CreateFromJoinElement(

if (fromElement.Parent == null)
{
// Most likely means association join is used in invalid context
// I.e. in subquery: from EntityA a where exists (from EntityB join a.Assocation)
// Maybe we should throw exception instead
fromElement.FromClause.AddChild(fromElement);
if (fromElement.IsImplied)
fromElement.JoinSequence.SetUseThetaStyle(true);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,6 @@ public JoinSequence CreateCollectionJoinSequence(IQueryableCollection collPersis
{
JoinSequence joinSequence = CreateJoinSequence();
joinSequence.SetRoot(collPersister, collectionName);
joinSequence.SetUseThetaStyle(true); // TODO: figure out how this should be set.

///////////////////////////////////////////////////////////////////////////////
// This was the reason for failures regarding INDEX_OP and subclass joins on
Expand Down
4 changes: 1 addition & 3 deletions src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -516,10 +516,8 @@ private void DereferenceEntityJoin(string classAlias, EntityType propertyType, b

if ( ! useFoundFromElement )
{
// 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(!forceLeftJoin && impliedJoin, propertyType, tableAlias, _joinType, joinColumns);
.CreateJoinSequence(false, propertyType, tableAlias, _joinType, joinColumns);

var factory = new FromElementFactory(
currentFromClause,
Expand Down
6 changes: 3 additions & 3 deletions src/NHibernate/Hql/Ast/ANTLR/Tree/FromElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -590,13 +590,13 @@ public void SetOrigin(FromElement origin, bool manyToMany)
}
else
{
if (!Walker.IsInFrom && !Walker.IsInSelect)
if ((IsImplied && !JoinSequence.IsThetaStyle) || Walker.IsInFrom || Walker.IsInSelect)
{
FromClause.AddChild(this);
origin.AddChild(this);
}
else
{
origin.AddChild(this);
FromClause.AddChild(this);
}
}
}
Expand Down
29 changes: 18 additions & 11 deletions src/NHibernate/Hql/Ast/ANTLR/Tree/FromElementFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ private FromElement CreateFromElementInSubselect(

string tableAlias = correlatedSubselect ? fromElement.TableAlias : null;

//To properly generete subselect implicit join is required by SqlGenerator
if (fromElement.IsImplied)
fromElement.JoinSequence.SetUseThetaStyle(true);

// If the from element isn't in the same clause, create a new from element.
if (fromElement.FromClause != _fromClause)
{
Expand Down Expand Up @@ -189,18 +193,18 @@ public FromElement CreateCollection(IQueryableCollection queryableCollection,
if (elementType.IsEntityType)
{
// A collection of entities...
elem = CreateEntityAssociation(role, roleAlias, joinType);
elem = CreateEntityAssociation(role, roleAlias, joinType, indexed);
}
else if (elementType.IsComponentType)
{
// A collection of components...
JoinSequence joinSequence = CreateJoinSequence(roleAlias, joinType);
JoinSequence joinSequence = CreateJoinSequence(roleAlias, joinType, indexed);
elem = CreateCollectionJoin(joinSequence, roleAlias);
}
else
{
// A collection of scalar elements...
JoinSequence joinSequence = CreateJoinSequence(roleAlias, joinType);
JoinSequence joinSequence = CreateJoinSequence(roleAlias, joinType, indexed);
elem = CreateCollectionJoin(joinSequence, roleAlias);
}

Expand Down Expand Up @@ -332,7 +336,8 @@ public FromElement CreateEntityJoin(
private FromElement CreateEntityAssociation(
string role,
string roleAlias,
JoinType joinType)
JoinType joinType,
bool implicitJoin)
{
FromElement elem;
IQueryable entityPersister = (IQueryable)_queryableCollection.ElementPersister;
Expand All @@ -346,7 +351,7 @@ private FromElement CreateEntityAssociation(
Log.Debug("createEntityAssociation() : One to many - path = {0} role = {1} associatedEntityName = {2}", _path, role, associatedEntityName);
}

var joinSequence = CreateJoinSequence(roleAlias, joinType);
var joinSequence = CreateJoinSequence(roleAlias, joinType, implicitJoin);

elem = CreateJoin(associatedEntityName, roleAlias, joinSequence, (EntityType) _queryableCollection.ElementType, false);
elem.UseFromFragment |= elem.IsImplied && elem.Walker.IsSubQuery;
Expand All @@ -358,7 +363,7 @@ private FromElement CreateEntityAssociation(
Log.Debug("createManyToMany() : path = {0} role = {1} associatedEntityName = {2}", _path, role, associatedEntityName);
}

elem = CreateManyToMany(role, associatedEntityName, roleAlias, entityPersister, (EntityType)_queryableCollection.ElementType, joinType);
elem = CreateManyToMany(role, associatedEntityName, roleAlias, entityPersister, (EntityType)_queryableCollection.ElementType, joinType, implicitJoin);
_fromClause.Walker.AddQuerySpaces(_queryableCollection);
}
elem.CollectionTableAlias = roleAlias;
Expand Down Expand Up @@ -401,15 +406,16 @@ private FromElement CreateManyToMany(
string roleAlias,
IEntityPersister entityPersister,
EntityType type,
JoinType joinType)
JoinType joinType,
bool implicitJoin)
{
FromElement elem;
SessionFactoryHelperExtensions sfh = _fromClause.SessionFactoryHelper;

if (_inElementsFunction /*implied*/ )
{
// For implied many-to-many, just add the end join.
JoinSequence joinSequence = CreateJoinSequence(roleAlias, joinType);
JoinSequence joinSequence = CreateJoinSequence(roleAlias, joinType, implicitJoin);
elem = CreateJoin(associatedEntityName, roleAlias, joinSequence, type, true);
}
else
Expand All @@ -421,23 +427,24 @@ private FromElement CreateManyToMany(
string[] secondJoinColumns = sfh.GetCollectionElementColumns(role, roleAlias);

// Add the second join, the one that ends in the destination table.
JoinSequence joinSequence = CreateJoinSequence(roleAlias, joinType);
JoinSequence joinSequence = CreateJoinSequence(roleAlias, joinType, implicitJoin);
joinSequence.AddJoin(sfh.GetElementAssociationType(_collectionType), tableAlias, joinType, secondJoinColumns);
elem = CreateJoin(associatedEntityName, tableAlias, joinSequence, type, false);
elem.UseFromFragment = true;
}
return elem;
}

private JoinSequence CreateJoinSequence(string roleAlias, JoinType joinType)
//TODO: Investigate why not implicit indexed collection join is incorrectly generated and get rid of implicitJoin parameter (check IndexNode.Resolve)
private JoinSequence CreateJoinSequence(string roleAlias, JoinType joinType, bool implicitJoin)
{
SessionFactoryHelperExtensions sessionFactoryHelper = _fromClause.SessionFactoryHelper;
string[] joinColumns = Columns;
if (_collectionType == null)
{
throw new InvalidOperationException("collectionType is null!");
}
return sessionFactoryHelper.CreateJoinSequence(_implied, _collectionType, roleAlias, joinType, joinColumns);
return sessionFactoryHelper.CreateJoinSequence(implicitJoin, _collectionType, roleAlias, joinType, joinColumns);
}

private FromElement CreateJoin(
Expand Down
2 changes: 1 addition & 1 deletion src/NHibernate/Hql/CollectionSubqueryFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using System.Text;
using NHibernate.Engine;
using NHibernate.SqlCommand;
using NHibernate.Util;

namespace NHibernate.Hql
{
Expand All @@ -16,6 +15,7 @@ public static string CreateCollectionSubquery(
{
try
{
joinSequence.SetUseThetaStyle(true);
JoinFragment join = joinSequence.ToJoinFragment(enabledFilters, true);
return new StringBuilder()
.Append("select ")
Expand Down
1 change: 0 additions & 1 deletion src/NHibernate/NHibernate.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

<PackageDescription>NHibernate is a mature, open source object-relational mapper for the .NET framework. It is actively developed, fully featured and used in thousands of successful projects.</PackageDescription>
<PackageTags>ORM; O/RM; DataBase; DAL; ObjectRelationalMapping; NHibernate; ADO.Net; Core</PackageTags>
<LangVersion>7.3</LangVersion>
</PropertyGroup>

<ItemGroup>
Expand Down