Skip to content

Commit 3f9d6d6

Browse files
maca88hazzik
authored andcommitted
Port Hibernate's Aggregate functions for subqueries
1 parent 0bdee94 commit 3f9d6d6

10 files changed

+279
-2
lines changed
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
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.Dialect;
13+
using NHibernate.Mapping.ByCode;
14+
using NUnit.Framework;
15+
16+
namespace NHibernate.Test.Hql
17+
{
18+
using System.Threading.Tasks;
19+
using System.Threading;
20+
[TestFixture]
21+
public class AggregateFunctionsWithSubSelectTestAsync : TestCaseMappingByCode
22+
{
23+
protected override HbmMapping GetMappings()
24+
{
25+
var mapper = new ModelMapper();
26+
mapper.Class<Person>(
27+
rc =>
28+
{
29+
rc.Id(x => x.Id, m => m.Generator(Generators.Native));
30+
rc.Property(x => x.Name);
31+
rc.Map(x => x.Localized, cm => cm.Cascade(Mapping.ByCode.Cascade.All), x => x.Element());
32+
});
33+
34+
mapper.Class<Document>(
35+
rc =>
36+
{
37+
rc.Id(x => x.Id, m => m.Generator(Generators.Native));
38+
rc.Property(x => x.Name);
39+
rc.Map(x => x.Contacts, cm => cm.Key(k => k.Column("position")), x => x.OneToMany());
40+
});
41+
42+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
43+
}
44+
45+
protected override void OnTearDown()
46+
{
47+
using (var session = OpenSession())
48+
using (var transaction = session.BeginTransaction())
49+
{
50+
session.Delete("from System.Object");
51+
52+
session.Flush();
53+
transaction.Commit();
54+
}
55+
}
56+
57+
protected override void OnSetUp()
58+
{
59+
using (var session = OpenSession())
60+
using (var transaction = session.BeginTransaction())
61+
{
62+
var document = new Document();
63+
var p1 = new Person();
64+
var p2 = new Person();
65+
66+
p1.Localized.Add(1, "p1.1");
67+
p1.Localized.Add(2, "p1.2");
68+
p2.Localized.Add(1, "p2.1");
69+
p2.Localized.Add(2, "p2.2");
70+
71+
document.Contacts.Add(1, p1);
72+
document.Contacts.Add(2, p2);
73+
74+
session.Persist(p1);
75+
session.Persist(p2);
76+
session.Persist(document);
77+
78+
transaction.Commit();
79+
}
80+
}
81+
82+
protected override bool AppliesTo(Dialect.Dialect dialect)
83+
{
84+
return TestDialect.SupportsAggregateInSubSelect;
85+
}
86+
87+
[TestCase("SUM", 4)]
88+
[TestCase("MIN", 2)]
89+
[TestCase("MAX", 2)]
90+
[TestCase("AVG", 2d)]
91+
public async Task TestAggregateFunctionAsync(string functionName, object result, CancellationToken cancellationToken = default(CancellationToken))
92+
{
93+
var query = "SELECT " +
94+
" d.Id, " +
95+
$" {functionName}(" +
96+
" (" +
97+
" SELECT COUNT(localized) " +
98+
" FROM Person p " +
99+
" LEFT JOIN p.Localized localized " +
100+
" WHERE p.Id = c.Id" +
101+
" )" +
102+
" ) AS LocalizedCount " +
103+
"FROM Document d " +
104+
"LEFT JOIN d.Contacts c " +
105+
"GROUP BY d.Id";
106+
107+
using (var session = OpenSession())
108+
using (var transaction = session.BeginTransaction())
109+
{
110+
var results = await (session.CreateQuery(query).ListAsync(cancellationToken));
111+
112+
Assert.That(results, Has.Count.EqualTo(1));
113+
var tuple = results[0] as object[];
114+
Assert.That(tuple, Is.Not.Null);
115+
Assert.That(tuple, Has.Length.EqualTo(2));
116+
Assert.That(tuple[1], Is.EqualTo(result));
117+
await (transaction.CommitAsync(cancellationToken));
118+
}
119+
}
120+
}
121+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
using NHibernate.Cfg.MappingSchema;
2+
using NHibernate.Dialect;
3+
using NHibernate.Mapping.ByCode;
4+
using NUnit.Framework;
5+
6+
namespace NHibernate.Test.Hql
7+
{
8+
[TestFixture]
9+
public class AggregateFunctionsWithSubSelectTest : TestCaseMappingByCode
10+
{
11+
protected override HbmMapping GetMappings()
12+
{
13+
var mapper = new ModelMapper();
14+
mapper.Class<Person>(
15+
rc =>
16+
{
17+
rc.Id(x => x.Id, m => m.Generator(Generators.Native));
18+
rc.Property(x => x.Name);
19+
rc.Map(x => x.Localized, cm => cm.Cascade(Mapping.ByCode.Cascade.All), x => x.Element());
20+
});
21+
22+
mapper.Class<Document>(
23+
rc =>
24+
{
25+
rc.Id(x => x.Id, m => m.Generator(Generators.Native));
26+
rc.Property(x => x.Name);
27+
rc.Map(x => x.Contacts, cm => cm.Key(k => k.Column("position")), x => x.OneToMany());
28+
});
29+
30+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
31+
}
32+
33+
protected override void OnTearDown()
34+
{
35+
using (var session = OpenSession())
36+
using (var transaction = session.BeginTransaction())
37+
{
38+
session.Delete("from System.Object");
39+
40+
session.Flush();
41+
transaction.Commit();
42+
}
43+
}
44+
45+
protected override void OnSetUp()
46+
{
47+
using (var session = OpenSession())
48+
using (var transaction = session.BeginTransaction())
49+
{
50+
var document = new Document();
51+
var p1 = new Person();
52+
var p2 = new Person();
53+
54+
p1.Localized.Add(1, "p1.1");
55+
p1.Localized.Add(2, "p1.2");
56+
p2.Localized.Add(1, "p2.1");
57+
p2.Localized.Add(2, "p2.2");
58+
59+
document.Contacts.Add(1, p1);
60+
document.Contacts.Add(2, p2);
61+
62+
session.Persist(p1);
63+
session.Persist(p2);
64+
session.Persist(document);
65+
66+
transaction.Commit();
67+
}
68+
}
69+
70+
protected override bool AppliesTo(Dialect.Dialect dialect)
71+
{
72+
return TestDialect.SupportsAggregateInSubSelect;
73+
}
74+
75+
[TestCase("SUM", 4)]
76+
[TestCase("MIN", 2)]
77+
[TestCase("MAX", 2)]
78+
[TestCase("AVG", 2d)]
79+
public void TestAggregateFunction(string functionName, object result)
80+
{
81+
var query = "SELECT " +
82+
" d.Id, " +
83+
$" {functionName}(" +
84+
" (" +
85+
" SELECT COUNT(localized) " +
86+
" FROM Person p " +
87+
" LEFT JOIN p.Localized localized " +
88+
" WHERE p.Id = c.Id" +
89+
" )" +
90+
" ) AS LocalizedCount " +
91+
"FROM Document d " +
92+
"LEFT JOIN d.Contacts c " +
93+
"GROUP BY d.Id";
94+
95+
using (var session = OpenSession())
96+
using (var transaction = session.BeginTransaction())
97+
{
98+
var results = session.CreateQuery(query).List();
99+
100+
Assert.That(results, Has.Count.EqualTo(1));
101+
var tuple = results[0] as object[];
102+
Assert.That(tuple, Is.Not.Null);
103+
Assert.That(tuple, Has.Length.EqualTo(2));
104+
Assert.That(tuple[1], Is.EqualTo(result));
105+
transaction.Commit();
106+
}
107+
}
108+
}
109+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System.Collections.Generic;
2+
3+
namespace NHibernate.Test.Hql
4+
{
5+
public class Person
6+
{
7+
public virtual int Id { get; set; }
8+
9+
public virtual string Name { get; set; }
10+
11+
public virtual IDictionary<int, string > Localized { get; set; } = new Dictionary<int, string>();
12+
}
13+
14+
public class Document
15+
{
16+
public virtual int Id { get; set; }
17+
18+
public virtual string Name { get; set; }
19+
20+
public virtual IDictionary<int, Person> Contacts { get; set; } = new Dictionary<int, Person>();
21+
}
22+
}

src/NHibernate.Test/TestDialect.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ public bool NativeGeneratorSupportsBulkInsertion
8282

8383
public virtual bool SupportsDuplicatedColumnAliases => true;
8484

85+
public virtual bool SupportsAggregateInSubSelect => false;
86+
8587
/// <summary>
8688
/// Supports inserting in a table without any column specified in the insert.
8789
/// </summary>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace NHibernate.Test.TestDialects
2+
{
3+
public class MySQL5TestDialect : TestDialect
4+
{
5+
public MySQL5TestDialect(Dialect.Dialect dialect)
6+
: base(dialect)
7+
{
8+
}
9+
10+
public override bool SupportsAggregateInSubSelect => true;
11+
}
12+
}

src/NHibernate.Test/TestDialects/Oracle10gTestDialect.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,7 @@ public Oracle10gTestDialect(Dialect.Dialect dialect) : base(dialect)
1010
/// Does not support SELECT FOR UPDATE with paging
1111
/// </summary>
1212
public override bool SupportsSelectForUpdateWithPaging => false;
13+
14+
public override bool SupportsAggregateInSubSelect => true;
1315
}
1416
}

src/NHibernate.Test/TestDialects/PostgreSQL83TestDialect.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,7 @@ public PostgreSQL83TestDialect(Dialect.Dialect dialect)
1616
/// Npgsql 3.2.4.1.
1717
/// </summary>
1818
public override bool SupportsUsingConnectionOnSystemTransactionPrepare => false;
19+
20+
public override bool SupportsAggregateInSubSelect => true;
1921
}
2022
}

src/NHibernate.Test/TestDialects/SQLiteTestDialect.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,7 @@ public override bool SupportsHavingWithoutGroupBy
5151
/// Does not support update locks
5252
/// </summary>
5353
public override bool SupportsSelectForUpdate => false;
54+
55+
public override bool SupportsAggregateInSubSelect => true;
5456
}
5557
}

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -611,15 +611,19 @@ identPrimary
611611
//## aggregateFunction:
612612
//## COUNT | 'sum' | 'avg' | 'max' | 'min';
613613
aggregate
614-
: ( op=SUM | op=AVG | op=MAX | op=MIN ) OPEN additiveExpression CLOSE
615-
-> ^(AGGREGATE[$op] additiveExpression)
614+
: ( op=SUM | op=AVG | op=MAX | op=MIN ) OPEN aggregateArgument CLOSE
615+
-> ^(AGGREGATE[$op] aggregateArgument)
616616
// Special case for count - It's 'parameters' can be keywords.
617617
| COUNT OPEN ( s=STAR | p=aggregateDistinctAll ) CLOSE
618618
-> {s == null}? ^(COUNT $p)
619619
-> ^(COUNT ^(ROW_STAR["*"]))
620620
| collectionExpr
621621
;
622622
623+
aggregateArgument
624+
: ( additiveExpression | selectStatement )
625+
;
626+
623627
aggregateDistinctAll
624628
: ( ( DISTINCT | ALL )? ( path | collectionExpr ) )
625629
;

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ constructor
240240
aggregateExpr
241241
: expr //p:propertyRef { resolve(#p); }
242242
| collectionFunction
243+
| selectStatement
243244
;
244245
245246
propertyFetch

0 commit comments

Comments
 (0)