Skip to content

Commit 0bdee94

Browse files
maca88hazzik
authored andcommitted
Port Hibernate's support subqueries in HQL as CASE statement alternatives
1 parent af3ad13 commit 0bdee94

File tree

10 files changed

+228
-15
lines changed

10 files changed

+228
-15
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//------------------------------------------------------------------------------
2+
// <auto-generated>
3+
// This code was generated by AsyncGenerator.
4+
//
5+
// Changes to this file may cause incorrect behavior and will be lost if
6+
// the code is regenerated.
7+
// </auto-generated>
8+
//------------------------------------------------------------------------------
9+
10+
11+
using NHibernate.Cfg.MappingSchema;
12+
using NHibernate.Mapping.ByCode;
13+
using NUnit.Framework;
14+
15+
namespace NHibernate.Test.Hql
16+
{
17+
using System.Threading.Tasks;
18+
[TestFixture]
19+
public class SubQueryTestAsync : TestCaseMappingByCode
20+
{
21+
protected override HbmMapping GetMappings()
22+
{
23+
var mapper = new ModelMapper();
24+
mapper.Class<Root>(
25+
rc =>
26+
{
27+
rc.Id(x => x.Id, m => m.Generator(Generators.Native));
28+
rc.Property(x => x.RootName);
29+
rc.ManyToOne(x => x.Branch);
30+
});
31+
32+
mapper.Class<Branch>(
33+
rc =>
34+
{
35+
rc.Id(x => x.Id, m => m.Generator(Generators.Native));
36+
rc.Property(x => x.BranchName);
37+
rc.Bag(x => x.Leafs, cm => cm.Cascade(Mapping.ByCode.Cascade.All), x => x.OneToMany());
38+
});
39+
mapper.Class<Leaf>(
40+
rc =>
41+
{
42+
rc.Id(x => x.Id, m => m.Generator(Generators.Native));
43+
rc.Property(x => x.LeafName);
44+
});
45+
46+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
47+
}
48+
49+
protected override bool AppliesTo(Dialect.Dialect dialect)
50+
{
51+
return dialect.SupportsScalarSubSelects;
52+
}
53+
54+
[TestCase("SELECT CASE WHEN l.id IS NOT NULL THEN (SELECT COUNT(r.id) FROM Root r) ELSE 0 END FROM Leaf l")]
55+
[TestCase("SELECT CASE WHEN (SELECT COUNT(r.id) FROM Root r) > 1 THEN 1 ELSE 0 END FROM Leaf l")]
56+
[TestCase("SELECT CASE WHEN l.id > 1 THEN 1 ELSE (SELECT COUNT(r.id) FROM Root r) END FROM Leaf l")]
57+
[TestCase("SELECT CASE (SELECT COUNT(r.id) FROM Root r) WHEN 1 THEN 1 ELSE 0 END FROM Leaf l")]
58+
[TestCase("SELECT CASE l.id WHEN (SELECT COUNT(r.id) FROM Root r) THEN 1 ELSE 0 END FROM Leaf l")]
59+
public async Task TestSubQueryAsync(string query)
60+
{
61+
using (var session = OpenSession())
62+
using (var transaction = session.BeginTransaction())
63+
{
64+
// Simple syntax check
65+
await (session.CreateQuery(query).ListAsync());
66+
await (transaction.CommitAsync());
67+
}
68+
}
69+
}
70+
}

src/NHibernate.Test/Async/NHSpecificTest/GH1879/FixtureByCode.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using System.Collections.Generic;
1313
using System.Linq;
1414
using NHibernate.Cfg.MappingSchema;
15+
using NHibernate.Exceptions;
1516
using NHibernate.Mapping.ByCode;
1617
using NHibernate.Type;
1718
using NUnit.Framework;
@@ -124,8 +125,7 @@ protected async Task AreEqualAsync<TResult>(
124125
{
125126
expectedResult = await (expectedQuery(session.Query<T>()).ToListAsync(cancellationToken));
126127
}
127-
catch (OperationCanceledException) { throw; }
128-
catch (Exception e)
128+
catch (GenericADOException e)
129129
{
130130
Assert.Ignore($"Not currently supported query: {e}");
131131
}

src/NHibernate.Test/Hql/Ast/SqlTranslationFixture.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,16 @@ public void CaseClauseWithMath()
2323
Assert.DoesNotThrow(() => GetSql(queryWithoutParen));
2424
}
2525

26+
[Test]
27+
public void SimpleCaseClauseWithMath()
28+
{
29+
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";
30+
Assert.DoesNotThrow(() => GetSql(query));
31+
32+
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";
33+
Assert.DoesNotThrow(() => GetSql(queryWithoutParen));
34+
}
35+
2636
[Test]
2737
public void Union()
2838
{
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using NHibernate.Cfg.MappingSchema;
2+
using NHibernate.Mapping.ByCode;
3+
using NUnit.Framework;
4+
5+
namespace NHibernate.Test.Hql
6+
{
7+
[TestFixture]
8+
public class SubQueryTest : TestCaseMappingByCode
9+
{
10+
protected override HbmMapping GetMappings()
11+
{
12+
var mapper = new ModelMapper();
13+
mapper.Class<Root>(
14+
rc =>
15+
{
16+
rc.Id(x => x.Id, m => m.Generator(Generators.Native));
17+
rc.Property(x => x.RootName);
18+
rc.ManyToOne(x => x.Branch);
19+
});
20+
21+
mapper.Class<Branch>(
22+
rc =>
23+
{
24+
rc.Id(x => x.Id, m => m.Generator(Generators.Native));
25+
rc.Property(x => x.BranchName);
26+
rc.Bag(x => x.Leafs, cm => cm.Cascade(Mapping.ByCode.Cascade.All), x => x.OneToMany());
27+
});
28+
mapper.Class<Leaf>(
29+
rc =>
30+
{
31+
rc.Id(x => x.Id, m => m.Generator(Generators.Native));
32+
rc.Property(x => x.LeafName);
33+
});
34+
35+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
36+
}
37+
38+
protected override bool AppliesTo(Dialect.Dialect dialect)
39+
{
40+
return dialect.SupportsScalarSubSelects;
41+
}
42+
43+
[TestCase("SELECT CASE WHEN l.id IS NOT NULL THEN (SELECT COUNT(r.id) FROM Root r) ELSE 0 END FROM Leaf l")]
44+
[TestCase("SELECT CASE WHEN (SELECT COUNT(r.id) FROM Root r) > 1 THEN 1 ELSE 0 END FROM Leaf l")]
45+
[TestCase("SELECT CASE WHEN l.id > 1 THEN 1 ELSE (SELECT COUNT(r.id) FROM Root r) END FROM Leaf l")]
46+
[TestCase("SELECT CASE (SELECT COUNT(r.id) FROM Root r) WHEN 1 THEN 1 ELSE 0 END FROM Leaf l")]
47+
[TestCase("SELECT CASE l.id WHEN (SELECT COUNT(r.id) FROM Root r) THEN 1 ELSE 0 END FROM Leaf l")]
48+
public void TestSubQuery(string query)
49+
{
50+
using (var session = OpenSession())
51+
using (var transaction = session.BeginTransaction())
52+
{
53+
// Simple syntax check
54+
session.CreateQuery(query).List();
55+
transaction.Commit();
56+
}
57+
}
58+
}
59+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System.Collections.Generic;
2+
3+
namespace NHibernate.Test.Hql
4+
{
5+
public class Root
6+
{
7+
public virtual int Id { get; set; }
8+
9+
public virtual string RootName { get; set; }
10+
11+
public virtual Branch Branch { get; set; }
12+
}
13+
14+
public class Branch
15+
{
16+
public virtual int Id { get; set; }
17+
18+
public virtual string BranchName { get; set; }
19+
20+
public virtual IList<Leaf> Leafs { get; set; }
21+
}
22+
23+
public class Leaf
24+
{
25+
public virtual int Id { get; set; }
26+
27+
public virtual string LeafName { get; set; }
28+
}
29+
}

src/NHibernate.Test/NHSpecificTest/GH1879/FixtureByCode.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Linq;
44
using NHibernate.Cfg.MappingSchema;
5+
using NHibernate.Exceptions;
56
using NHibernate.Mapping.ByCode;
67
using NHibernate.Type;
78
using NUnit.Framework;
@@ -111,7 +112,7 @@ protected void AreEqual<TResult>(
111112
{
112113
expectedResult = expectedQuery(session.Query<T>()).ToList();
113114
}
114-
catch (Exception e)
115+
catch (GenericADOException e)
115116
{
116117
Assert.Ignore($"Not currently supported query: {e}");
117118
}

src/NHibernate/Hql/Ast/ANTLR/Hql.g

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -527,23 +527,31 @@ unaryExpression
527527
;
528528
529529
caseExpression
530-
: CASE (whenClause)+ (elseClause)? END
531-
-> ^(CASE whenClause+ elseClause?)
532-
| CASE unaryExpression (altWhenClause)+ (elseClause)? END
533-
-> ^(CASE2 unaryExpression altWhenClause+ elseClause?)
530+
: simpleCaseStatement
531+
| searchedCaseStatement
534532
;
535-
536-
whenClause
537-
: (WHEN^ logicalExpression THEN! expression)
533+
534+
simpleCaseStatement
535+
: CASE expression (simpleCaseWhenClause)+ (elseClause)? END
536+
-> ^(CASE2 expression simpleCaseWhenClause+ elseClause?)
538537
;
539-
540-
altWhenClause
541-
: (WHEN^ unaryExpression THEN! expression)
538+
539+
simpleCaseWhenClause
540+
: (WHEN^ expression THEN! expression)
542541
;
543542
544543
elseClause
545544
: (ELSE^ expression)
546545
;
546+
547+
searchedCaseStatement
548+
: CASE (searchedCaseWhenClause)+ (elseClause)? END
549+
-> ^(CASE searchedCaseWhenClause+ elseClause?)
550+
;
551+
552+
searchedCaseWhenClause
553+
: (WHEN^ logicalExpression THEN! expression)
554+
;
547555
548556
quantifiedExpression
549557
: ( SOME^ | EXISTS^ | ALL^ | ANY^ )

src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.g

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -432,8 +432,28 @@ arithmeticExpr
432432
;
433433
434434
caseExpr
435-
: ^(CASE { _inCase = true; } (^(WHEN logicalExpr expr))+ (^(ELSE expr))?) { _inCase = false; }
436-
| ^(CASE2 { _inCase = true; } expr (^(WHEN expr expr))+ (^(ELSE expr))?) { _inCase = false; }
435+
: simpleCaseExpression
436+
| searchedCaseExpression
437+
;
438+
439+
simpleCaseExpression
440+
: ^(CASE2 {_inCase=true;} exprOrSubquery (simpleCaseWhenClause)+ (elseClause)?) {_inCase=false;}
441+
;
442+
443+
simpleCaseWhenClause
444+
: ^(WHEN exprOrSubquery exprOrSubquery)
445+
;
446+
447+
elseClause
448+
: ^(ELSE exprOrSubquery)
449+
;
450+
451+
searchedCaseExpression
452+
: ^(CASE {_inCase = true;} (searchedCaseWhenClause)+ (elseClause)?) {_inCase = false;}
453+
;
454+
455+
searchedCaseWhenClause
456+
: ^(WHEN logicalExpr exprOrSubquery)
437457
;
438458
439459
//TODO: I don't think we need this anymore .. how is it different to

src/NHibernate/Linq/ReWriters/ConditionalQueryReferenceExpander.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ public override void VisitSelectClause(SelectClause selectClause, QueryModel que
3131
_expander.Transform(selectClause);
3232
}
3333

34+
public override void VisitMainFromClause(MainFromClause fromClause, QueryModel queryModel)
35+
{
36+
if (fromClause.FromExpression is SubQueryExpression subqueryExpression)
37+
{
38+
VisitQueryModel(subqueryExpression.QueryModel);
39+
}
40+
}
41+
3442
public override void VisitOrdering(Ordering ordering, QueryModel queryModel, OrderByClause orderByClause, int index)
3543
{
3644
_expander.Transform(ordering);

src/NHibernate/Linq/ReWriters/SubQueryConditionalExpander.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ public override void VisitSelectClause(SelectClause selectClause, QueryModel que
2424
_expander.Transform(selectClause);
2525
}
2626

27+
public override void VisitMainFromClause(MainFromClause fromClause, QueryModel queryModel)
28+
{
29+
if (fromClause.FromExpression is SubQueryExpression subqueryExpression)
30+
{
31+
VisitQueryModel(subqueryExpression.QueryModel);
32+
}
33+
}
34+
2735
public override void VisitOrdering(Ordering ordering, QueryModel queryModel, OrderByClause orderByClause, int index)
2836
{
2937
_expander.Transform(ordering);

0 commit comments

Comments
 (0)