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;