diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH1228/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH1228/Fixture.cs
new file mode 100644
index 00000000000..a95df9e5f21
--- /dev/null
+++ b/src/NHibernate.Test/Async/NHSpecificTest/GH1228/Fixture.cs
@@ -0,0 +1,106 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by AsyncGenerator.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+
+using NUnit.Framework;
+
+namespace NHibernate.Test.NHSpecificTest.GH1228
+{
+ using System.Threading.Tasks;
+ [TestFixture]
+ public class FixtureAsync : BugTestCase
+ {
+ [Test]
+ public async Task TestThetaJoinOnAssociationInSubQueryAsync()
+ {
+ using var s = OpenSession();
+ var queryThatWorks = s.CreateQuery(
+ @"
+ SELECT ROOT FROM NHibernate.Test.NHSpecificTest.GH1228.Sheet AS ROOT
+ WHERE (EXISTS (FROM NHibernate.Test.NHSpecificTest.GH1228.Shelf AS inv
+ , ROOT.Folder AS ROOT_Folder
+ WHERE ROOT_Folder.Shelf = inv AND inv.Id = 1
+ ))
+ AND ROOT.Name = 'SomeName'");
+ await (queryThatWorks.ListAsync());
+
+ queryThatWorks = s.CreateQuery(
+ @"
+ SELECT ROOT FROM NHibernate.Test.NHSpecificTest.GH1228.Shelf AS ROOT
+ WHERE (EXISTS (FROM NHibernate.Test.NHSpecificTest.GH1228.Sheet AS sheet
+ , ROOT.Folders AS ROOT_Folder
+ WHERE ROOT_Folder = sheet.Folder AND sheet.Name = 'SomeName'
+ ))
+ AND ROOT.Id = 1");
+ await (queryThatWorks.ListAsync());
+ }
+
+ [Test]
+ public async Task TestAnsiJoinOnAssociationInSubQueryAsync()
+ {
+ if (!TestDialect.SupportsCorrelatedColumnsInSubselectJoin)
+ Assert.Ignore("Dialect doesn't support this test case");
+
+ using var s = OpenSession();
+ var queryThatCreatesWrongSQL = s.CreateQuery(
+ @"
+ SELECT ROOT FROM NHibernate.Test.NHSpecificTest.GH1228.Sheet AS ROOT
+ WHERE (EXISTS (FROM NHibernate.Test.NHSpecificTest.GH1228.Shelf AS inv
+ JOIN ROOT.Folder AS ROOT_Folder
+ WHERE ROOT_Folder.Shelf = inv AND inv.Id = 1
+ ))
+ AND ROOT.Name = 'SomeName'");
+ await (queryThatCreatesWrongSQL.ListAsync());
+
+ // The only assertion here is that the generated SQL is valid and can be executed.
+ // With the bug, the generated SQL is missing the JOIN inside the subselect (EXISTS) to Folder.
+ queryThatCreatesWrongSQL = s.CreateQuery(
+ @"
+ SELECT ROOT FROM NHibernate.Test.NHSpecificTest.GH1228.Shelf AS ROOT
+ WHERE (EXISTS (FROM NHibernate.Test.NHSpecificTest.GH1228.Sheet AS sheet
+ JOIN ROOT.Folders AS ROOT_Folder
+ WHERE ROOT_Folder = sheet.Folder AND sheet.Name = 'SomeName'
+ ))
+ AND ROOT.Id = 1");
+ await (queryThatCreatesWrongSQL.ListAsync());
+ // The only assertion here is that the generated SQL is valid and can be executed.
+ // With the bug, the generated SQL is missing the JOIN inside the subselect (EXISTS) to Folder.
+ }
+
+ [Test]
+ public async Task TestOtherAnsiJoinOnAssociationInSubQueryAsync()
+ {
+ using var s = OpenSession();
+
+ // The only assertion here is that the generated SQL is valid and can be executed.
+ // With the bug, the generated SQL is missing the JOIN inside the subselect (EXISTS) to Folder.
+ var queryThatCreatesWrongSQL = s.CreateQuery(
+ @"
+ SELECT ROOT FROM NHibernate.Test.NHSpecificTest.GH1228.Shelf AS ROOT
+ WHERE (EXISTS (FROM NHibernate.Test.NHSpecificTest.GH1228.Sheet AS sheet
+ JOIN sheet.Folder AS folder
+ WHERE folder.Shelf = ROOT AND sheet.Name = 'SomeName'
+ ))
+ AND ROOT.Id = 1");
+ await (queryThatCreatesWrongSQL.ListAsync());
+
+ // The only assertion here is that the generated SQL is valid and can be executed.
+ // With the bug, the generated SQL is missing the JOIN inside the subselect (EXISTS) to Folder.
+ queryThatCreatesWrongSQL = s.CreateQuery(
+ @"
+ SELECT ROOT FROM NHibernate.Test.NHSpecificTest.GH1228.Sheet AS ROOT
+ WHERE (EXISTS (FROM NHibernate.Test.NHSpecificTest.GH1228.Shelf AS inv
+ JOIN inv.Folders AS folder
+ WHERE folder = ROOT.Folder AND inv.Id = 1
+ ))
+ AND ROOT.Name = 'SomeName'");
+ await (queryThatCreatesWrongSQL.ListAsync());
+ }
+ }
+}
diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH3334/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH3334/Fixture.cs
new file mode 100644
index 00000000000..b25d10169a7
--- /dev/null
+++ b/src/NHibernate.Test/Async/NHSpecificTest/GH3334/Fixture.cs
@@ -0,0 +1,204 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by AsyncGenerator.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using NUnit.Framework;
+
+namespace NHibernate.Test.NHSpecificTest.GH3334
+{
+ using System.Threading.Tasks;
+ [TestFixture]
+ public class FixtureAsync : BugTestCase
+ {
+ [OneTimeSetUp]
+ public void OneTimeSetUp()
+ {
+ using var session = OpenSession();
+ using var t = session.BeginTransaction();
+ var parent = new Entity
+ {
+ Name = "Parent1",
+ Children = { new ChildEntity { Name = "Child", Child = new GrandChildEntity { Name = "GrandChild" } } }
+ };
+ session.Save(parent);
+ parent = new Entity
+ {
+ Name = "Parent2",
+ Children = { new ChildEntity { Name = "Child", Child = new GrandChildEntity { Name = "XGrandChild" } } }
+ };
+ var other = new OtherEntity { Name = "ABC", Entities = {parent}};
+ parent.OtherEntity = other;
+ session.Save(parent);
+ session.Save(other);
+ t.Commit();
+ }
+
+ [OneTimeTearDown]
+ public void OneTimeTearDown()
+ {
+ using var session = OpenSession();
+ using var transaction = session.BeginTransaction();
+
+ session.CreateQuery("delete from ChildEntity").ExecuteUpdate();
+ session.CreateQuery("delete from GrandChildEntity").ExecuteUpdate();
+ session.CreateQuery("delete from Entity").ExecuteUpdate();
+ session.CreateQuery("delete from OtherEntity").ExecuteUpdate();
+
+ transaction.Commit();
+ }
+
+ protected override bool AppliesTo(Dialect.Dialect dialect)
+ {
+ return TestDialect.SupportsCorrelatedColumnsInSubselectJoin;
+ }
+
+ public class TestCaseItem
+ {
+ public string Name { get; }
+ public string Hql { get; }
+ public int LineNumber { get; }
+
+ public TestCaseItem(string name, string hql, [CallerLineNumber] int lineNumber = 0)
+ {
+ Name = name;
+ Hql = hql;
+ LineNumber = lineNumber;
+ }
+
+ public override string ToString() => $"{LineNumber:0000}: {Name}";
+ }
+
+ public static IEnumerable GetNoExceptionOnExecuteQueryTestCases()
+ {
+ /* does not work because of inner join or theta join created for many-to-one
+ @"
+ SELECT ROOT
+ FROM Entity AS ROOT
+ WHERE
+ EXISTS
+ (FROM ELEMENTS(ROOT.Children) AS child
+ WHERE
+ child.Child.Name like 'G%'
+ OR ROOT.OtherEntity.Name like 'A%'
+ )");*/
+
+ yield return new("Basic Elements case 1 FoundViaGrandChildG", @"
+ SELECT ROOT
+ FROM Entity AS ROOT
+ WHERE
+ EXISTS
+ (FROM ELEMENTS(ROOT.Children) AS child
+ LEFT JOIN child.Child AS grandChild
+ WHERE
+ grandChild.Name like 'G%'
+ )");
+ yield return new("Basic Elements case 2 FoundViaOtherEntityA", @"
+ SELECT ROOT
+ FROM Entity AS ROOT
+ WHERE
+ EXISTS
+ (FROM ELEMENTS(ROOT.OtherEntity) AS otherEntity
+ WHERE
+ otherEntity.Name like 'A%'
+ )");
+ yield return new("HQL Elements FoundViaGrandChildG", @"
+ SELECT ROOT
+ FROM Entity AS ROOT
+ WHERE
+ EXISTS
+ (FROM ELEMENTS(ROOT.Children) AS child
+ LEFT JOIN child.Child AS grandChild
+ LEFT JOIN ROOT.OtherEntity AS otherEntity
+ WHERE
+ grandChild.Name like 'G%'
+ OR otherEntity.Name like 'G%'
+ )");
+ yield return new("HQL Elements FoundViaOtherEntityA", @"
+ SELECT ROOT
+ FROM Entity AS ROOT
+ WHERE
+ EXISTS
+ (FROM ELEMENTS(ROOT.Children) AS child
+ LEFT JOIN child.Child AS grandChild
+ LEFT JOIN ROOT.OtherEntity AS otherEntity
+ WHERE
+ grandChild.Name like 'A%'
+ OR otherEntity.Name like 'A%'
+ )");
+ yield return new("HQL Entity FoundViaGrandChildG", @"
+ SELECT ROOT
+ FROM Entity AS ROOT
+ WHERE
+ EXISTS
+ (FROM ChildEntity AS child
+ LEFT JOIN child.Child AS grandChild
+ LEFT JOIN ROOT.OtherEntity AS otherEntity
+ WHERE
+ child.Parent = ROOT
+ AND (
+ grandChild.Name like 'G%'
+ OR otherEntity.Name like 'G%'
+ )
+ )");
+ yield return new("HQL Entity FoundViaOtherEntityA", @"
+ SELECT ROOT
+ FROM Entity AS ROOT
+ WHERE
+ EXISTS
+ (FROM ChildEntity AS child
+ LEFT JOIN child.Child AS grandChild
+ LEFT JOIN ROOT.OtherEntity AS otherEntity
+ WHERE
+ child.Parent = ROOT
+ AND (
+ grandChild.Name like 'A%'
+ OR otherEntity.Name like 'A%'
+ )
+ )");
+ yield return new("FROM ROOT.Children FoundViaGrandChildG", @"
+ SELECT ROOT
+ FROM Entity AS ROOT
+ WHERE
+ EXISTS
+ (FROM ROOT.Children AS child
+ LEFT JOIN child.Child AS grandChild
+ WHERE
+ grandChild.Name like 'G%'
+ )");
+ yield return new("FROM ROOT.OtherEntity FoundViaOtherEntityA", @"
+ SELECT ROOT
+ FROM Entity AS ROOT
+ WHERE
+ EXISTS
+ (FROM ROOT.OtherEntity AS otherEntity
+ LEFT JOIN ChildEntity AS child ON child.Parent = ROOT
+ LEFT JOIN child.Child AS grandChild
+ WHERE
+ grandChild.Name like 'A%'
+ OR otherEntity.Name like 'A%'
+ )");
+ }
+
+ [Test, TestCaseSource(nameof(GetNoExceptionOnExecuteQueryTestCases))]
+ public async Task NoExceptionOnExecuteQueryAsync(TestCaseItem testCase)
+ {
+ using var session = OpenSession();
+ var q = session.CreateQuery(testCase.Hql);
+ Assert.That(await (q.ListAsync()), Has.Count.EqualTo(1));
+ }
+
+ protected override bool CheckDatabaseWasCleaned()
+ {
+ // same set of objects for each test
+ return true;
+ }
+ }
+}
diff --git a/src/NHibernate.Test/NHSpecificTest/GH1228/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH1228/Fixture.cs
index 940b721a90b..64e4af85e3e 100644
--- a/src/NHibernate.Test/NHSpecificTest/GH1228/Fixture.cs
+++ b/src/NHibernate.Test/NHSpecificTest/GH1228/Fixture.cs
@@ -2,132 +2,94 @@
namespace NHibernate.Test.NHSpecificTest.GH1228
{
+ [TestFixture]
public class Fixture : BugTestCase
{
[Test]
- public void TestOk()
+ public void TestThetaJoinOnAssociationInSubQuery()
{
- using (ISession s = OpenSession())
- {
- using (ITransaction t = s.BeginTransaction())
- {
- try
- {
- {
- var queryThatWorks = s.CreateQuery(@"
- SELECT ROOT FROM NHibernate.Test.NHSpecificTest.GH1228.Sheet AS ROOT
- WHERE (EXISTS (FROM NHibernate.Test.NHSpecificTest.GH1228.Shelf AS inv
- , ROOT.Folder AS ROOT_Folder
- WHERE ROOT_Folder.Shelf = inv AND inv.Id = 1
- ) )
- AND ROOT.Name = 'SomeName'");
- queryThatWorks.List();
- }
- {
- var queryThatWorks = s.CreateQuery(@"
- SELECT ROOT FROM NHibernate.Test.NHSpecificTest.GH1228.Shelf AS ROOT
- WHERE (EXISTS (FROM NHibernate.Test.NHSpecificTest.GH1228.Sheet AS sheet
- , ROOT.Folders AS ROOT_Folder
- WHERE ROOT_Folder = sheet.Folder AND sheet.Name = 'SomeName'
- ) )
- AND ROOT.Id = 1");
- queryThatWorks.List();
- }
- }
- finally
- {
- s.Delete("from Sheet");
- s.Delete("from Folder");
- s.Delete("from Shelf");
- t.Commit();
- }
- }
- }
+ using var s = OpenSession();
+ var queryThatWorks = s.CreateQuery(
+ @"
+ SELECT ROOT FROM NHibernate.Test.NHSpecificTest.GH1228.Sheet AS ROOT
+ WHERE (EXISTS (FROM NHibernate.Test.NHSpecificTest.GH1228.Shelf AS inv
+ , ROOT.Folder AS ROOT_Folder
+ WHERE ROOT_Folder.Shelf = inv AND inv.Id = 1
+ ))
+ AND ROOT.Name = 'SomeName'");
+ queryThatWorks.List();
+
+ queryThatWorks = s.CreateQuery(
+ @"
+ SELECT ROOT FROM NHibernate.Test.NHSpecificTest.GH1228.Shelf AS ROOT
+ WHERE (EXISTS (FROM NHibernate.Test.NHSpecificTest.GH1228.Sheet AS sheet
+ , ROOT.Folders AS ROOT_Folder
+ WHERE ROOT_Folder = sheet.Folder AND sheet.Name = 'SomeName'
+ ))
+ AND ROOT.Id = 1");
+ queryThatWorks.List();
}
[Test]
- public void TestWrongSql()
+ public void TestAnsiJoinOnAssociationInSubQuery()
{
- using (ISession s = OpenSession())
- {
- using (ITransaction t = s.BeginTransaction())
- {
- try
- {
- {
- var queryThatCreatesWrongSQL = s.CreateQuery(@"
- SELECT ROOT FROM NHibernate.Test.NHSpecificTest.GH1228.Sheet AS ROOT
- WHERE (EXISTS (FROM NHibernate.Test.NHSpecificTest.GH1228.Shelf AS inv
- JOIN ROOT.Folder AS ROOT_Folder
- WHERE ROOT_Folder.Shelf = inv AND inv.Id = 1
- ) )
- AND ROOT.Name = 'SomeName'");
- queryThatCreatesWrongSQL.List();
- }
- {
- // The only assertion here is that the generated SQL is valid and can be executed.
- // Right now, the generated SQL is missing the JOIN inside the subselect (EXISTS) to Folder.
- var queryThatCreatesWrongSQL = s.CreateQuery(@"
- SELECT ROOT FROM NHibernate.Test.NHSpecificTest.GH1228.Shelf AS ROOT
- WHERE (EXISTS (FROM NHibernate.Test.NHSpecificTest.GH1228.Sheet AS sheet
- JOIN ROOT.Folders AS ROOT_Folder
- WHERE ROOT_Folder = sheet.Folder AND sheet.Name = 'SomeName'
- ) )
- AND ROOT.Id = 1");
- queryThatCreatesWrongSQL.List();
- // The only assertion here is that the generated SQL is valid and can be executed.
- // Right now, the generated SQL is missing the JOIN inside the subselect (EXISTS) to Folder.
- }
- }
- finally
- {
- s.Delete("from Sheet");
- s.Delete("from Folder");
- s.Delete("from Shelf");
- t.Commit();
- }
- }
- }
+ if (!TestDialect.SupportsCorrelatedColumnsInSubselectJoin)
+ Assert.Ignore("Dialect doesn't support this test case");
+
+ using var s = OpenSession();
+ var queryThatCreatesWrongSQL = s.CreateQuery(
+ @"
+ SELECT ROOT FROM NHibernate.Test.NHSpecificTest.GH1228.Sheet AS ROOT
+ WHERE (EXISTS (FROM NHibernate.Test.NHSpecificTest.GH1228.Shelf AS inv
+ JOIN ROOT.Folder AS ROOT_Folder
+ WHERE ROOT_Folder.Shelf = inv AND inv.Id = 1
+ ))
+ AND ROOT.Name = 'SomeName'");
+ queryThatCreatesWrongSQL.List();
+
+ // The only assertion here is that the generated SQL is valid and can be executed.
+ // With the bug, the generated SQL is missing the JOIN inside the subselect (EXISTS) to Folder.
+ queryThatCreatesWrongSQL = s.CreateQuery(
+ @"
+ SELECT ROOT FROM NHibernate.Test.NHSpecificTest.GH1228.Shelf AS ROOT
+ WHERE (EXISTS (FROM NHibernate.Test.NHSpecificTest.GH1228.Sheet AS sheet
+ JOIN ROOT.Folders AS ROOT_Folder
+ WHERE ROOT_Folder = sheet.Folder AND sheet.Name = 'SomeName'
+ ))
+ AND ROOT.Id = 1");
+ queryThatCreatesWrongSQL.List();
+ // The only assertion here is that the generated SQL is valid and can be executed.
+ // With the bug, the generated SQL is missing the JOIN inside the subselect (EXISTS) to Folder.
}
[Test]
- public void Test3() {
- using (ISession s = OpenSession()) {
- using (ITransaction t = s.BeginTransaction()) {
- try {
- {
- // The only assertion here is that the generated SQL is valid and can be executed.
- // Right now, the generated SQL is missing the JOIN inside the subselect (EXISTS) to Folder.
- var queryThatCreatesWrongSQL = s.CreateQuery(@"
- SELECT ROOT FROM NHibernate.Test.NHSpecificTest.GH1228.Shelf AS ROOT
- WHERE (EXISTS (FROM NHibernate.Test.NHSpecificTest.GH1228.Sheet AS sheet
- JOIN sheet.Folder AS folder
- WHERE folder.Shelf = ROOT AND sheet.Name = 'SomeName'
- ) )
- AND ROOT.Id = 1");
- queryThatCreatesWrongSQL.List();
- // The only assertion here is that the generated SQL is valid and can be executed.
- // Right now, the generated SQL is missing the JOIN inside the subselect (EXISTS) to Folder.
- }
- {
- var queryThatCreatesWrongSQL = s.CreateQuery(@"
- SELECT ROOT FROM NHibernate.Test.NHSpecificTest.GH1228.Sheet AS ROOT
- WHERE (EXISTS (FROM NHibernate.Test.NHSpecificTest.GH1228.Shelf AS inv
- JOIN inv.Folders AS folder
- WHERE folder = ROOT.Folder AND inv.Id = 1
- ) )
- AND ROOT.Name = 'SomeName'");
- queryThatCreatesWrongSQL.List();
- }
- }
- finally {
- s.Delete("from Sheet");
- s.Delete("from Folder");
- s.Delete("from Shelf");
- t.Commit();
- }
- }
- }
+ public void TestOtherAnsiJoinOnAssociationInSubQuery()
+ {
+ using var s = OpenSession();
+
+ // The only assertion here is that the generated SQL is valid and can be executed.
+ // With the bug, the generated SQL is missing the JOIN inside the subselect (EXISTS) to Folder.
+ var queryThatCreatesWrongSQL = s.CreateQuery(
+ @"
+ SELECT ROOT FROM NHibernate.Test.NHSpecificTest.GH1228.Shelf AS ROOT
+ WHERE (EXISTS (FROM NHibernate.Test.NHSpecificTest.GH1228.Sheet AS sheet
+ JOIN sheet.Folder AS folder
+ WHERE folder.Shelf = ROOT AND sheet.Name = 'SomeName'
+ ))
+ AND ROOT.Id = 1");
+ queryThatCreatesWrongSQL.List();
+
+ // The only assertion here is that the generated SQL is valid and can be executed.
+ // With the bug, the generated SQL is missing the JOIN inside the subselect (EXISTS) to Folder.
+ queryThatCreatesWrongSQL = s.CreateQuery(
+ @"
+ SELECT ROOT FROM NHibernate.Test.NHSpecificTest.GH1228.Sheet AS ROOT
+ WHERE (EXISTS (FROM NHibernate.Test.NHSpecificTest.GH1228.Shelf AS inv
+ JOIN inv.Folders AS folder
+ WHERE folder = ROOT.Folder AND inv.Id = 1
+ ))
+ AND ROOT.Name = 'SomeName'");
+ queryThatCreatesWrongSQL.List();
}
}
}
diff --git a/src/NHibernate.Test/NHSpecificTest/GH3334/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH3334/Entity.cs
new file mode 100644
index 00000000000..718d3ac3488
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/GH3334/Entity.cs
@@ -0,0 +1,33 @@
+using System.Collections.Generic;
+
+namespace NHibernate.Test.NHSpecificTest.GH3334
+{
+ public class Entity
+ {
+ public virtual int Id { get; set; }
+ public virtual string Name { get; set; }
+ public virtual ISet Children { get; set; } = new HashSet();
+ public virtual OtherEntity OtherEntity { get; set; }
+ }
+
+ public class ChildEntity
+ {
+ public virtual int Id { get; set; }
+ public virtual Entity Parent { get; set; }
+ public virtual string Name { get; set; }
+ public virtual GrandChildEntity Child { get; set; }
+ }
+
+ public class GrandChildEntity
+ {
+ public virtual int Id { get; set; }
+ public virtual string Name { get; set; }
+ }
+
+ public class OtherEntity
+ {
+ public virtual int Id { get; set; }
+ public virtual string Name { get; set; }
+ public virtual ISet Entities { get; set; } = new HashSet();
+ }
+}
diff --git a/src/NHibernate.Test/NHSpecificTest/GH3334/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH3334/Fixture.cs
new file mode 100644
index 00000000000..8ebf4b1d29c
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/GH3334/Fixture.cs
@@ -0,0 +1,193 @@
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using NUnit.Framework;
+
+namespace NHibernate.Test.NHSpecificTest.GH3334
+{
+ [TestFixture]
+ public class Fixture : BugTestCase
+ {
+ [OneTimeSetUp]
+ public void OneTimeSetUp()
+ {
+ using var session = OpenSession();
+ using var t = session.BeginTransaction();
+ var parent = new Entity
+ {
+ Name = "Parent1",
+ Children = { new ChildEntity { Name = "Child", Child = new GrandChildEntity { Name = "GrandChild" } } }
+ };
+ session.Save(parent);
+ parent = new Entity
+ {
+ Name = "Parent2",
+ Children = { new ChildEntity { Name = "Child", Child = new GrandChildEntity { Name = "XGrandChild" } } }
+ };
+ var other = new OtherEntity { Name = "ABC", Entities = {parent}};
+ parent.OtherEntity = other;
+ session.Save(parent);
+ session.Save(other);
+ t.Commit();
+ }
+
+ [OneTimeTearDown]
+ public void OneTimeTearDown()
+ {
+ using var session = OpenSession();
+ using var transaction = session.BeginTransaction();
+
+ session.CreateQuery("delete from ChildEntity").ExecuteUpdate();
+ session.CreateQuery("delete from GrandChildEntity").ExecuteUpdate();
+ session.CreateQuery("delete from Entity").ExecuteUpdate();
+ session.CreateQuery("delete from OtherEntity").ExecuteUpdate();
+
+ transaction.Commit();
+ }
+
+ protected override bool AppliesTo(Dialect.Dialect dialect)
+ {
+ return TestDialect.SupportsCorrelatedColumnsInSubselectJoin;
+ }
+
+ public class TestCaseItem
+ {
+ public string Name { get; }
+ public string Hql { get; }
+ public int LineNumber { get; }
+
+ public TestCaseItem(string name, string hql, [CallerLineNumber] int lineNumber = 0)
+ {
+ Name = name;
+ Hql = hql;
+ LineNumber = lineNumber;
+ }
+
+ public override string ToString() => $"{LineNumber:0000}: {Name}";
+ }
+
+ public static IEnumerable GetNoExceptionOnExecuteQueryTestCases()
+ {
+ /* does not work because of inner join or theta join created for many-to-one
+ @"
+ SELECT ROOT
+ FROM Entity AS ROOT
+ WHERE
+ EXISTS
+ (FROM ELEMENTS(ROOT.Children) AS child
+ WHERE
+ child.Child.Name like 'G%'
+ OR ROOT.OtherEntity.Name like 'A%'
+ )");*/
+
+ yield return new("Basic Elements case 1 FoundViaGrandChildG", @"
+ SELECT ROOT
+ FROM Entity AS ROOT
+ WHERE
+ EXISTS
+ (FROM ELEMENTS(ROOT.Children) AS child
+ LEFT JOIN child.Child AS grandChild
+ WHERE
+ grandChild.Name like 'G%'
+ )");
+ yield return new("Basic Elements case 2 FoundViaOtherEntityA", @"
+ SELECT ROOT
+ FROM Entity AS ROOT
+ WHERE
+ EXISTS
+ (FROM ELEMENTS(ROOT.OtherEntity) AS otherEntity
+ WHERE
+ otherEntity.Name like 'A%'
+ )");
+ yield return new("HQL Elements FoundViaGrandChildG", @"
+ SELECT ROOT
+ FROM Entity AS ROOT
+ WHERE
+ EXISTS
+ (FROM ELEMENTS(ROOT.Children) AS child
+ LEFT JOIN child.Child AS grandChild
+ LEFT JOIN ROOT.OtherEntity AS otherEntity
+ WHERE
+ grandChild.Name like 'G%'
+ OR otherEntity.Name like 'G%'
+ )");
+ yield return new("HQL Elements FoundViaOtherEntityA", @"
+ SELECT ROOT
+ FROM Entity AS ROOT
+ WHERE
+ EXISTS
+ (FROM ELEMENTS(ROOT.Children) AS child
+ LEFT JOIN child.Child AS grandChild
+ LEFT JOIN ROOT.OtherEntity AS otherEntity
+ WHERE
+ grandChild.Name like 'A%'
+ OR otherEntity.Name like 'A%'
+ )");
+ yield return new("HQL Entity FoundViaGrandChildG", @"
+ SELECT ROOT
+ FROM Entity AS ROOT
+ WHERE
+ EXISTS
+ (FROM ChildEntity AS child
+ LEFT JOIN child.Child AS grandChild
+ LEFT JOIN ROOT.OtherEntity AS otherEntity
+ WHERE
+ child.Parent = ROOT
+ AND (
+ grandChild.Name like 'G%'
+ OR otherEntity.Name like 'G%'
+ )
+ )");
+ yield return new("HQL Entity FoundViaOtherEntityA", @"
+ SELECT ROOT
+ FROM Entity AS ROOT
+ WHERE
+ EXISTS
+ (FROM ChildEntity AS child
+ LEFT JOIN child.Child AS grandChild
+ LEFT JOIN ROOT.OtherEntity AS otherEntity
+ WHERE
+ child.Parent = ROOT
+ AND (
+ grandChild.Name like 'A%'
+ OR otherEntity.Name like 'A%'
+ )
+ )");
+ yield return new("FROM ROOT.Children FoundViaGrandChildG", @"
+ SELECT ROOT
+ FROM Entity AS ROOT
+ WHERE
+ EXISTS
+ (FROM ROOT.Children AS child
+ LEFT JOIN child.Child AS grandChild
+ WHERE
+ grandChild.Name like 'G%'
+ )");
+ yield return new("FROM ROOT.OtherEntity FoundViaOtherEntityA", @"
+ SELECT ROOT
+ FROM Entity AS ROOT
+ WHERE
+ EXISTS
+ (FROM ROOT.OtherEntity AS otherEntity
+ LEFT JOIN ChildEntity AS child ON child.Parent = ROOT
+ LEFT JOIN child.Child AS grandChild
+ WHERE
+ grandChild.Name like 'A%'
+ OR otherEntity.Name like 'A%'
+ )");
+ }
+
+ [Test, TestCaseSource(nameof(GetNoExceptionOnExecuteQueryTestCases))]
+ public void NoExceptionOnExecuteQuery(TestCaseItem testCase)
+ {
+ using var session = OpenSession();
+ var q = session.CreateQuery(testCase.Hql);
+ Assert.That(q.List(), Has.Count.EqualTo(1));
+ }
+
+ protected override bool CheckDatabaseWasCleaned()
+ {
+ // same set of objects for each test
+ return true;
+ }
+ }
+}
diff --git a/src/NHibernate.Test/NHSpecificTest/GH3334/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH3334/Mappings.hbm.xml
new file mode 100644
index 00000000000..1f5bcdfe8e6
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/GH3334/Mappings.hbm.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/NHibernate.Test/TestDialect.cs b/src/NHibernate.Test/TestDialect.cs
index 49b2f7e0f69..e7bc20d304f 100644
--- a/src/NHibernate.Test/TestDialect.cs
+++ b/src/NHibernate.Test/TestDialect.cs
@@ -203,5 +203,10 @@ public bool SupportsSqlType(SqlType sqlType)
/// Returns true if you can cancel a query.
///
public virtual bool SupportsCancelQuery => true;
+
+ ///
+ /// Some databases (MySql) don't support using main table aliases in subquery inside join ON clause
+ ///
+ public virtual bool SupportsCorrelatedColumnsInSubselectJoin => true;
}
}
diff --git a/src/NHibernate.Test/TestDialects/MySQL5TestDialect.cs b/src/NHibernate.Test/TestDialects/MySQL5TestDialect.cs
index 0ec57f43bc6..ea68e6f19b7 100644
--- a/src/NHibernate.Test/TestDialects/MySQL5TestDialect.cs
+++ b/src/NHibernate.Test/TestDialects/MySQL5TestDialect.cs
@@ -14,5 +14,12 @@ public MySQL5TestDialect(Dialect.Dialect dialect)
/// This behaviour is documented at: http://dev.mysql.com/doc/refman/5.6/en/update.html
///
public override bool SupportsModifyAndSelectSameTable => false;
+
+ ///
+ /// A correlated column can be present only in the subquery's WHERE clause (and not in the SELECT list,
+ /// a JOIN or ORDER BY clause, a GROUP BY list, or a HAVING clause). Nor can there be any correlated column inside a derived table in the subquery's FROM list.
+ /// See https://dev.mysql.com/doc/refman/8.0/en/correlated-subqueries.html
+ ///
+ public override bool SupportsCorrelatedColumnsInSubselectJoin => false;
}
}
diff --git a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs
index ac22725e582..8628201c18b 100644
--- a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs
+++ b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs
@@ -832,16 +832,6 @@ void CreateFromJoinElement(
HandleWithFragment(fromElement, with);
}
-
- 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);
- }
}
if ( log.IsDebugEnabled() )
@@ -930,7 +920,7 @@ private static string GetPropertyPath(DotNode dotNode, IASTNode alias)
return lhs.Path + "." + path;
}
- IASTNode CreateFromElement(string path, IASTNode pathNode, IASTNode alias, IASTNode propertyFetch)
+ FromElement CreateFromElement(string path, IASTNode pathNode, IASTNode alias, IASTNode propertyFetch)
{
FromElement fromElement = _currentFromClause.AddFromElement(path, alias);
SetPropertyFetch(fromElement, propertyFetch, alias);
diff --git a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.g b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.g
index 47b42f286ea..623cc830fd8 100644
--- a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.g
+++ b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.g
@@ -266,10 +266,14 @@ fromElementList @init{
fromElement!
@init {
- IASTNode fromElement = null;
+ FromElement fromElement = null;
}
// A simple class name, alias element.
- : ^(RANGE p=path (a=ALIAS)? (pf=propertyFetch)? ) { fromElement = CreateFromElement($p.p, $p.tree, $a, $pf.tree); }
+ : ^(RANGE p=path (a=ALIAS)? (pf=propertyFetch)? )
+ {
+ fromElement = CreateFromElement($p.p, $p.tree, $a, $pf.tree);
+ fromElement.JoinSequence.SetUseThetaStyle(true);
+ }
-> {fromElement != null}? ^({fromElement})
->
| je=joinElement
diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromClause.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromClause.cs
index 39179f1419a..839ea736e5d 100644
--- a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromClause.cs
+++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromClause.cs
@@ -447,6 +447,11 @@ internal void FinishInit()
dependentElement.Parent.InsertChild(index, item);
}
}
+
+ if (_appendFromElements.Count > 0)
+ {
+ _fromElements[0].JoinSequence.SetUseThetaStyle(true);
+ }
_appendFromElements.Clear();
}
diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElement.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElement.cs
index 79d42962748..a22c13e84ff 100644
--- a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElement.cs
+++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElement.cs
@@ -707,12 +707,18 @@ public void SetOrigin(FromElement origin, bool manyToMany)
{
// HHH-276 : implied joins in a subselect where clause - The destination needs to be added
// to the destination's from clause.
- FromClause.AddChild(this); // Not sure if this is will fix everything, but it works.
+ FromClause.AddChild(this);
+
+ // Generate correlated implied joins inside subquery implicitly
+ // As some dialects (MySql) do not support correlated columns to be used in subquery join ON clause
+ if (IsImplied)
+ {
+ JoinSequence.SetUseThetaStyle(true);
+ }
}
else
{
- // Otherwise, the destination node was implied by the FROM clause and the FROM clause processor
- // will automatically add it in the right place.
+ FromClause.AppendFromElement(this);
}
}
diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElementFactory.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElementFactory.cs
index 4f80d774f43..e9a95769f1f 100644
--- a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElementFactory.cs
+++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElementFactory.cs
@@ -122,10 +122,6 @@ 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)
{
@@ -321,7 +317,7 @@ public FromElement CreateEntityJoin(
// 1) 'elem' is the "root from-element" in correlated subqueries
// 2) The DotNode.useThetaStyleImplicitJoins has been set to true
// and 'elem' represents an implicit join
- if (elem.FromClause != elem.Origin.FromClause || DotNode.UseThetaStyleImplicitJoins)
+ if (DotNode.UseThetaStyleImplicitJoins)
{
// the "root from-element" in correlated subqueries do need this piece
elem.Type = HqlSqlWalker.FROM_FRAGMENT;