Skip to content

Commit 344821e

Browse files
committed
Port Hibernate's support subqueries in HQL as CASE statement alternatives
1 parent 151194f commit 344821e

File tree

5 files changed

+200
-12
lines changed

5 files changed

+200
-12
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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+
using System.Threading;
19+
[TestFixture]
20+
public class SubQueryTestAsync : TestCaseMappingByCode
21+
{
22+
protected override HbmMapping GetMappings()
23+
{
24+
var mapper = new ModelMapper();
25+
mapper.Class<Root>(
26+
rc =>
27+
{
28+
rc.Id(x => x.Id, m => m.Generator(Generators.Native));
29+
rc.Property(x => x.RootName);
30+
rc.ManyToOne(x => x.Branch);
31+
});
32+
33+
mapper.Class<Branch>(
34+
rc =>
35+
{
36+
rc.Id(x => x.Id, m => m.Generator(Generators.Native));
37+
rc.Property(x => x.BranchName);
38+
rc.Bag(x => x.Leafs, cm => cm.Cascade(Mapping.ByCode.Cascade.All), x => x.OneToMany());
39+
});
40+
mapper.Class<Leaf>(
41+
rc =>
42+
{
43+
rc.Id(x => x.Id, m => m.Generator(Generators.Native));
44+
rc.Property(x => x.LeafName);
45+
});
46+
47+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
48+
}
49+
50+
[TestCase("SELECT CASE WHEN l.id IS NOT NULL THEN (SELECT COUNT(r.id) FROM Root r) ELSE 0 END FROM Leaf l")]
51+
[TestCase("SELECT CASE WHEN (SELECT COUNT(r.id) FROM Root r) > 1 THEN 1 ELSE 0 END FROM Leaf l")]
52+
[TestCase("SELECT CASE WHEN l.id > 1 THEN 1 ELSE (SELECT COUNT(r.id) FROM Root r) END FROM Leaf l")]
53+
[TestCase("SELECT CASE (SELECT COUNT(r.id) FROM Root r) WHEN 1 THEN 1 ELSE 0 END FROM Leaf l")]
54+
[TestCase("SELECT CASE l.id WHEN (SELECT COUNT(r.id) FROM Root r) THEN 1 ELSE 0 END FROM Leaf l")]
55+
public async Task TestSubQueryAsync(string query, CancellationToken cancellationToken = default(CancellationToken))
56+
{
57+
if (!Dialect.SupportsScalarSubSelects)
58+
{
59+
Assert.Ignore(Dialect.GetType().Name + " does not support scalar sub-queries");
60+
}
61+
62+
using (var session = OpenSession())
63+
using (var transaction = session.BeginTransaction())
64+
{
65+
// Simple syntax check
66+
await (session.CreateQuery(query).ListAsync(cancellationToken));
67+
await (transaction.CommitAsync(cancellationToken));
68+
}
69+
}
70+
}
71+
}
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+
[TestCase("SELECT CASE WHEN l.id IS NOT NULL THEN (SELECT COUNT(r.id) FROM Root r) ELSE 0 END FROM Leaf l")]
39+
[TestCase("SELECT CASE WHEN (SELECT COUNT(r.id) FROM Root r) > 1 THEN 1 ELSE 0 END FROM Leaf l")]
40+
[TestCase("SELECT CASE WHEN l.id > 1 THEN 1 ELSE (SELECT COUNT(r.id) FROM Root r) END FROM Leaf l")]
41+
[TestCase("SELECT CASE (SELECT COUNT(r.id) FROM Root r) WHEN 1 THEN 1 ELSE 0 END FROM Leaf l")]
42+
[TestCase("SELECT CASE l.id WHEN (SELECT COUNT(r.id) FROM Root r) THEN 1 ELSE 0 END FROM Leaf l")]
43+
public void TestSubQuery(string query)
44+
{
45+
if (!Dialect.SupportsScalarSubSelects)
46+
{
47+
Assert.Ignore(Dialect.GetType().Name + " does not support scalar sub-queries");
48+
}
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/Hql/Ast/ANTLR/Hql.g

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -527,23 +527,32 @@ 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+
// NOTE : the unaryExpression rule contains the subQuery rule
531+
: simpleCaseStatement
532+
| searchedCaseStatement
534533
;
535-
536-
whenClause
537-
: (WHEN^ logicalExpression THEN! expression)
534+
535+
simpleCaseStatement
536+
: CASE unaryExpression (simpleCaseWhenClause)+ (elseClause)? END
537+
-> ^(CASE2 unaryExpression simpleCaseWhenClause+ elseClause?)
538538
;
539-
540-
altWhenClause
541-
: (WHEN^ unaryExpression THEN! expression)
539+
540+
simpleCaseWhenClause
541+
: (WHEN^ unaryExpression THEN! unaryExpression)
542542
;
543543
544544
elseClause
545545
: (ELSE^ expression)
546546
;
547+
548+
searchedCaseStatement
549+
: CASE (searchedCaseWhenClause)+ (elseClause)? END
550+
-> ^(CASE searchedCaseWhenClause+ elseClause?)
551+
;
552+
553+
searchedCaseWhenClause
554+
: (WHEN^ logicalExpression THEN! unaryExpression)
555+
;
547556
548557
quantifiedExpression
549558
: ( 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

0 commit comments

Comments
 (0)