diff --git a/src/NHibernate.Test/Async/Hql/SubQueryTest.cs b/src/NHibernate.Test/Async/Hql/SubQueryTest.cs new file mode 100644 index 00000000000..f88c5ed3629 --- /dev/null +++ b/src/NHibernate.Test/Async/Hql/SubQueryTest.cs @@ -0,0 +1,70 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.Hql +{ + using System.Threading.Tasks; + [TestFixture] + public class SubQueryTestAsync : TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class( + rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.Native)); + rc.Property(x => x.RootName); + rc.ManyToOne(x => x.Branch); + }); + + mapper.Class( + rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.Native)); + rc.Property(x => x.BranchName); + rc.Bag(x => x.Leafs, cm => cm.Cascade(Mapping.ByCode.Cascade.All), x => x.OneToMany()); + }); + mapper.Class( + rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.Native)); + rc.Property(x => x.LeafName); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return dialect.SupportsScalarSubSelects; + } + + [TestCase("SELECT CASE WHEN l.id IS NOT NULL THEN (SELECT COUNT(r.id) FROM Root r) ELSE 0 END FROM Leaf l")] + [TestCase("SELECT CASE WHEN (SELECT COUNT(r.id) FROM Root r) > 1 THEN 1 ELSE 0 END FROM Leaf l")] + [TestCase("SELECT CASE WHEN l.id > 1 THEN 1 ELSE (SELECT COUNT(r.id) FROM Root r) END FROM Leaf l")] + [TestCase("SELECT CASE (SELECT COUNT(r.id) FROM Root r) WHEN 1 THEN 1 ELSE 0 END FROM Leaf l")] + [TestCase("SELECT CASE l.id WHEN (SELECT COUNT(r.id) FROM Root r) THEN 1 ELSE 0 END FROM Leaf l")] + public async Task TestSubQueryAsync(string query) + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + // Simple syntax check + await (session.CreateQuery(query).ListAsync()); + await (transaction.CommitAsync()); + } + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH1879/FixtureByCode.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH1879/FixtureByCode.cs index d57b65898b5..0dac0e740f5 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH1879/FixtureByCode.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH1879/FixtureByCode.cs @@ -12,6 +12,7 @@ using System.Collections.Generic; using System.Linq; using NHibernate.Cfg.MappingSchema; +using NHibernate.Exceptions; using NHibernate.Mapping.ByCode; using NHibernate.Type; using NUnit.Framework; @@ -124,8 +125,7 @@ protected async Task AreEqualAsync( { expectedResult = await (expectedQuery(session.Query()).ToListAsync(cancellationToken)); } - catch (OperationCanceledException) { throw; } - catch (Exception e) + catch (GenericADOException e) { Assert.Ignore($"Not currently supported query: {e}"); } diff --git a/src/NHibernate.Test/Hql/Ast/SqlTranslationFixture.cs b/src/NHibernate.Test/Hql/Ast/SqlTranslationFixture.cs index 2e2cd4cddff..ab734057d4f 100644 --- a/src/NHibernate.Test/Hql/Ast/SqlTranslationFixture.cs +++ b/src/NHibernate.Test/Hql/Ast/SqlTranslationFixture.cs @@ -23,6 +23,16 @@ public void CaseClauseWithMath() Assert.DoesNotThrow(() => GetSql(queryWithoutParen)); } + [Test] + public void SimpleCaseClauseWithMath() + { + const string query = "from SimpleClass s where (case (cast(s.IntValue as long) * :pAValue) when (cast(s.IntValue as long) * :pAValue) then (cast(s.IntValue as long) * :pAValue) else 1 end) > 0"; + Assert.DoesNotThrow(() => GetSql(query)); + + const string queryWithoutParen = "from SimpleClass s where (case cast(s.IntValue as long) * :pAValue when cast(s.IntValue as long) * :pAValue then cast(s.IntValue as long) * :pAValue else 1 end) > 0"; + Assert.DoesNotThrow(() => GetSql(queryWithoutParen)); + } + [Test] public void Union() { diff --git a/src/NHibernate.Test/Hql/SubQueryTest.cs b/src/NHibernate.Test/Hql/SubQueryTest.cs new file mode 100644 index 00000000000..caf665de998 --- /dev/null +++ b/src/NHibernate.Test/Hql/SubQueryTest.cs @@ -0,0 +1,59 @@ +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.Hql +{ + [TestFixture] + public class SubQueryTest : TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class( + rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.Native)); + rc.Property(x => x.RootName); + rc.ManyToOne(x => x.Branch); + }); + + mapper.Class( + rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.Native)); + rc.Property(x => x.BranchName); + rc.Bag(x => x.Leafs, cm => cm.Cascade(Mapping.ByCode.Cascade.All), x => x.OneToMany()); + }); + mapper.Class( + rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.Native)); + rc.Property(x => x.LeafName); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return dialect.SupportsScalarSubSelects; + } + + [TestCase("SELECT CASE WHEN l.id IS NOT NULL THEN (SELECT COUNT(r.id) FROM Root r) ELSE 0 END FROM Leaf l")] + [TestCase("SELECT CASE WHEN (SELECT COUNT(r.id) FROM Root r) > 1 THEN 1 ELSE 0 END FROM Leaf l")] + [TestCase("SELECT CASE WHEN l.id > 1 THEN 1 ELSE (SELECT COUNT(r.id) FROM Root r) END FROM Leaf l")] + [TestCase("SELECT CASE (SELECT COUNT(r.id) FROM Root r) WHEN 1 THEN 1 ELSE 0 END FROM Leaf l")] + [TestCase("SELECT CASE l.id WHEN (SELECT COUNT(r.id) FROM Root r) THEN 1 ELSE 0 END FROM Leaf l")] + public void TestSubQuery(string query) + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + // Simple syntax check + session.CreateQuery(query).List(); + transaction.Commit(); + } + } + } +} diff --git a/src/NHibernate.Test/Hql/SubQueryTestEntities.cs b/src/NHibernate.Test/Hql/SubQueryTestEntities.cs new file mode 100644 index 00000000000..2b431eeacf7 --- /dev/null +++ b/src/NHibernate.Test/Hql/SubQueryTestEntities.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; + +namespace NHibernate.Test.Hql +{ + public class Root + { + public virtual int Id { get; set; } + + public virtual string RootName { get; set; } + + public virtual Branch Branch { get; set; } + } + + public class Branch + { + public virtual int Id { get; set; } + + public virtual string BranchName { get; set; } + + public virtual IList Leafs { get; set; } + } + + public class Leaf + { + public virtual int Id { get; set; } + + public virtual string LeafName { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH1879/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH1879/FixtureByCode.cs index 3fe9211a449..6b491f59d00 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH1879/FixtureByCode.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH1879/FixtureByCode.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using NHibernate.Cfg.MappingSchema; +using NHibernate.Exceptions; using NHibernate.Mapping.ByCode; using NHibernate.Type; using NUnit.Framework; @@ -111,7 +112,7 @@ protected void AreEqual( { expectedResult = expectedQuery(session.Query()).ToList(); } - catch (Exception e) + catch (GenericADOException e) { Assert.Ignore($"Not currently supported query: {e}"); } diff --git a/src/NHibernate/Hql/Ast/ANTLR/Hql.g b/src/NHibernate/Hql/Ast/ANTLR/Hql.g index 2783936a19a..de0b52d8a7c 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Hql.g +++ b/src/NHibernate/Hql/Ast/ANTLR/Hql.g @@ -527,23 +527,31 @@ unaryExpression ; caseExpression - : CASE (whenClause)+ (elseClause)? END - -> ^(CASE whenClause+ elseClause?) - | CASE unaryExpression (altWhenClause)+ (elseClause)? END - -> ^(CASE2 unaryExpression altWhenClause+ elseClause?) + : simpleCaseStatement + | searchedCaseStatement ; - -whenClause - : (WHEN^ logicalExpression THEN! expression) + +simpleCaseStatement + : CASE expression (simpleCaseWhenClause)+ (elseClause)? END + -> ^(CASE2 expression simpleCaseWhenClause+ elseClause?) ; - -altWhenClause - : (WHEN^ unaryExpression THEN! expression) + +simpleCaseWhenClause + : (WHEN^ expression THEN! expression) ; elseClause : (ELSE^ expression) ; + +searchedCaseStatement + : CASE (searchedCaseWhenClause)+ (elseClause)? END + -> ^(CASE searchedCaseWhenClause+ elseClause?) + ; + +searchedCaseWhenClause + : (WHEN^ logicalExpression THEN! expression) + ; quantifiedExpression : ( SOME^ | EXISTS^ | ALL^ | ANY^ ) diff --git a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.g b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.g index 78825998019..befd1f3a2e5 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.g +++ b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.g @@ -432,8 +432,28 @@ arithmeticExpr ; caseExpr - : ^(CASE { _inCase = true; } (^(WHEN logicalExpr expr))+ (^(ELSE expr))?) { _inCase = false; } - | ^(CASE2 { _inCase = true; } expr (^(WHEN expr expr))+ (^(ELSE expr))?) { _inCase = false; } + : simpleCaseExpression + | searchedCaseExpression + ; + +simpleCaseExpression + : ^(CASE2 {_inCase=true;} exprOrSubquery (simpleCaseWhenClause)+ (elseClause)?) {_inCase=false;} + ; + +simpleCaseWhenClause + : ^(WHEN exprOrSubquery exprOrSubquery) + ; + +elseClause + : ^(ELSE exprOrSubquery) + ; + +searchedCaseExpression + : ^(CASE {_inCase = true;} (searchedCaseWhenClause)+ (elseClause)?) {_inCase = false;} + ; + +searchedCaseWhenClause + : ^(WHEN logicalExpr exprOrSubquery) ; //TODO: I don't think we need this anymore .. how is it different to diff --git a/src/NHibernate/Linq/ReWriters/ConditionalQueryReferenceExpander.cs b/src/NHibernate/Linq/ReWriters/ConditionalQueryReferenceExpander.cs index b83be7077b4..c67039b1863 100644 --- a/src/NHibernate/Linq/ReWriters/ConditionalQueryReferenceExpander.cs +++ b/src/NHibernate/Linq/ReWriters/ConditionalQueryReferenceExpander.cs @@ -31,6 +31,14 @@ public override void VisitSelectClause(SelectClause selectClause, QueryModel que _expander.Transform(selectClause); } + public override void VisitMainFromClause(MainFromClause fromClause, QueryModel queryModel) + { + if (fromClause.FromExpression is SubQueryExpression subqueryExpression) + { + VisitQueryModel(subqueryExpression.QueryModel); + } + } + public override void VisitOrdering(Ordering ordering, QueryModel queryModel, OrderByClause orderByClause, int index) { _expander.Transform(ordering); diff --git a/src/NHibernate/Linq/ReWriters/SubQueryConditionalExpander.cs b/src/NHibernate/Linq/ReWriters/SubQueryConditionalExpander.cs index 3c6d3a4231f..5319018b021 100644 --- a/src/NHibernate/Linq/ReWriters/SubQueryConditionalExpander.cs +++ b/src/NHibernate/Linq/ReWriters/SubQueryConditionalExpander.cs @@ -24,6 +24,14 @@ public override void VisitSelectClause(SelectClause selectClause, QueryModel que _expander.Transform(selectClause); } + public override void VisitMainFromClause(MainFromClause fromClause, QueryModel queryModel) + { + if (fromClause.FromExpression is SubQueryExpression subqueryExpression) + { + VisitQueryModel(subqueryExpression.QueryModel); + } + } + public override void VisitOrdering(Ordering ordering, QueryModel queryModel, OrderByClause orderByClause, int index) { _expander.Transform(ordering);