Skip to content

Commit 7e83aff

Browse files
PleasantDhazzik
authored andcommitted
NH-3844 - Tests and fix by flattening array index access during group key nomination (#455)
1 parent b7c085c commit 7e83aff

File tree

6 files changed

+301
-0
lines changed

6 files changed

+301
-0
lines changed

src/NHibernate.Test/Linq/ByMethod/GroupByTests.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,20 @@ public void GroupByComputedValueInObjectArrayWithJoinInRightSideOfCase()
715715
Assert.AreEqual(2155, orderGroups.Sum(g => g.Count));
716716
}
717717

718+
[Test(Description = "NH-3844")]
719+
public void GroupByComputedValueFromNestedArraySelect()
720+
{
721+
var orderGroups = db.OrderLines.Select(o => new object[] { o }).GroupBy(x => new object[] { ((OrderLine)x[0]).Order.Customer == null ? 0 : 1 }).Select(g => new { Key = g.Key, Count = g.Count() }).ToList();
722+
Assert.AreEqual(2155, orderGroups.Sum(g => g.Count));
723+
}
724+
725+
[Test(Description = "NH-3844")]
726+
public void GroupByComputedValueFromNestedObjectSelect()
727+
{
728+
var orderGroups = db.OrderLines.Select(o => new { OrderLine = (object)o }).GroupBy(x => new object[] { ((OrderLine)x.OrderLine).Order.Customer == null ? 0 : 1 }).Select(g => new { Key = g.Key, Count = g.Count() }).ToList();
729+
Assert.AreEqual(2155, orderGroups.Sum(g => g.Count));
730+
}
731+
718732
private static void CheckGrouping<TKey, TElement>(IEnumerable<IGrouping<TKey, TElement>> groupedItems, Func<TElement, TKey> groupBy)
719733
{
720734
var used = new HashSet<object>();
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
6+
namespace NHibernate.Test.NHSpecificTest.NH3844
7+
{
8+
public class Project
9+
{
10+
public Project()
11+
{
12+
Components = new List<Component>();
13+
}
14+
15+
public virtual Guid Id { get; set; }
16+
public virtual string Name { get; set; }
17+
public virtual IList<Component> Components { get; set; }
18+
public virtual Job Job { get; set; }
19+
}
20+
21+
public class Component
22+
{
23+
public virtual Guid Id { get; set; }
24+
public virtual string Name { get; set; }
25+
public virtual Project Project { get; set; }
26+
}
27+
28+
public class TimeRecord
29+
{
30+
public TimeRecord()
31+
{
32+
Components = new List<Component>();
33+
}
34+
35+
public virtual Guid Id { get; set; }
36+
public virtual double TimeInHours { get; set; }
37+
public virtual Project Project { get; set; }
38+
public virtual IList<Component> Components { get; set; }
39+
40+
}
41+
42+
public class Job
43+
{
44+
public virtual Guid Id { get; set; }
45+
public virtual string Name { get; set; }
46+
public virtual BillingType BillingType { get; set; }
47+
}
48+
49+
public enum BillingType
50+
{
51+
None,
52+
Hourly,
53+
Fixed,
54+
}
55+
}
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Linq.Expressions;
6+
using System.Reflection;
7+
using System.Text;
8+
using System.Threading;
9+
using NHibernate.Linq;
10+
using NHibernate.Test.ExceptionsTest;
11+
using NHibernate.Test.MappingByCode;
12+
using NUnit.Framework;
13+
14+
namespace NHibernate.Test.NHSpecificTest.NH3844
15+
{
16+
[TestFixture]
17+
public class Fixture : BugTestCase
18+
{
19+
protected override void OnSetUp()
20+
{
21+
var job1 = new Job { Name = "Not a Job", BillingType = BillingType.None };
22+
var job2 = new Job { Name = "Contract Job", BillingType = BillingType.Fixed };
23+
var job3 = new Job { Name = "Pay as You Go Job", BillingType = BillingType.Hourly };
24+
25+
var project1 = new Project { Name = "ProjectOne", Job = job1 };
26+
var compP1_x = new Component() { Name = "P1x", Project = project1 };
27+
var compP1_y = new Component() { Name = "P1y", Project = project1 };
28+
29+
var project2 = new Project { Name = "ProjectTwo", Job = job2 };
30+
var compP2_x = new Component() { Name = "P2x", Project = project2 };
31+
var compP2_y = new Component() { Name = "P2y", Project = project2 };
32+
33+
var project3 = new Project { Name = "ProjectThree", Job = job3 };
34+
var compP3_x = new Component() { Name = "P3x", Project = project3 };
35+
var compP3_y = new Component() { Name = "P3y", Project = project3 };
36+
37+
using (var session = OpenSession())
38+
using (var transaction = session.BeginTransaction())
39+
{
40+
session.Save(job1);
41+
session.Save(project1);
42+
session.Save(compP1_x);
43+
session.Save(compP1_y);
44+
session.Save(job2);
45+
session.Save(project2);
46+
session.Save(compP2_x);
47+
session.Save(compP2_y);
48+
session.Save(job3);
49+
session.Save(project3);
50+
session.Save(compP3_x);
51+
session.Save(compP3_y);
52+
53+
54+
session.Save(new TimeRecord { TimeInHours = 1, Project = project1, Components = { } });
55+
session.Save(new TimeRecord { TimeInHours = 2, Project = project1, Components = { compP1_x } });
56+
session.Save(new TimeRecord { TimeInHours = 3, Project = project1, Components = { compP1_y } });
57+
session.Save(new TimeRecord { TimeInHours = 4, Project = project1, Components = { compP1_x, compP1_y } });
58+
59+
session.Save(new TimeRecord { TimeInHours = 5, Project = project2, Components = { } });
60+
session.Save(new TimeRecord { TimeInHours = 6, Project = project2, Components = { compP2_x } });
61+
session.Save(new TimeRecord { TimeInHours = 7, Project = project2, Components = { compP2_y } });
62+
session.Save(new TimeRecord { TimeInHours = 8, Project = project2, Components = { compP2_x, compP2_y } });
63+
64+
session.Save(new TimeRecord { TimeInHours = 9, Project = project3, Components = { } });
65+
session.Save(new TimeRecord { TimeInHours = 10, Project = project3, Components = { compP3_x } });
66+
session.Save(new TimeRecord { TimeInHours = 11, Project = project3, Components = { compP3_y } });
67+
session.Save(new TimeRecord { TimeInHours = 12, Project = project3, Components = { compP3_x, compP3_y } });
68+
69+
transaction.Commit();
70+
}
71+
}
72+
73+
protected override void OnTearDown()
74+
{
75+
using (var session = OpenSession())
76+
using (var transaction = session.BeginTransaction())
77+
{
78+
session.Delete("from TimeRecord");
79+
session.Delete("from Component");
80+
session.Delete("from Project");
81+
session.Delete("from Job");
82+
83+
transaction.Commit();
84+
}
85+
}
86+
87+
[Test]
88+
public void ConditionalGroupKeyFromArrayAccess()
89+
{
90+
using (var session = OpenSession())
91+
using (var transaction = session.BeginTransaction())
92+
{
93+
var baseQuery = session.Query<TimeRecord>();
94+
95+
Assert.That(baseQuery.Sum(x => x.TimeInHours), Is.EqualTo(78));
96+
97+
var query = baseQuery.Select(t => new object[] { t })
98+
.GroupBy(j => new object[] { ((TimeRecord)j[0]).Project.Job.BillingType == BillingType.None ? 0 : 1 }, j => (TimeRecord)j[0])
99+
.Select(g => new object[] { g.Key, g.Count(), g.Sum(t => (decimal?)t.TimeInHours) });
100+
101+
var results = query.ToList().OrderBy(x => (int)((object[])x[0])[0]);
102+
Assert.That(results.Select(x => x[1]), Is.EquivalentTo(new[] { 4, 8 }));
103+
Assert.That(results.Select(x => x[2]), Is.EquivalentTo(new[] { 10, 68 }));
104+
105+
Assert.That(results.Sum(x => (decimal?)x[2]), Is.EqualTo(78));
106+
107+
transaction.Rollback();
108+
}
109+
}
110+
111+
[Test]
112+
public void ConditionalGroupKeyFromSubqueryArrayAccess()
113+
{
114+
using (var session = OpenSession())
115+
using (var transaction = session.BeginTransaction())
116+
{
117+
var baseQuery = session.Query<TimeRecord>();
118+
119+
Assert.That(baseQuery.Sum(x => x.TimeInHours), Is.EqualTo(78));
120+
121+
var query = baseQuery.Select(t => new object[] { t })
122+
.SelectMany(t => ((TimeRecord)t[0]).Components.Select(c => (object)c.Id).DefaultIfEmpty().Select(c => new[] { t[0], c }))
123+
.GroupBy(j => new object[] { ((TimeRecord)j[0]).Project.Job.BillingType == BillingType.None ? 0 : 1 }, j => (TimeRecord)j[0])
124+
.Select(g => new object[] { g.Key, g.Count(), g.Sum(t => (decimal?)t.TimeInHours) });
125+
126+
var results = query.ToList().OrderBy(x => (int)((object[])x[0])[0]);
127+
Assert.That(results.Select(x => x[1]), Is.EquivalentTo(new[] { 5, 10 }));
128+
Assert.That(results.Select(x => x[2]), Is.EquivalentTo(new[] { 14, 88 }));
129+
130+
Assert.That(results.Sum(x => (decimal?)x[2]), Is.EqualTo(102));
131+
132+
transaction.Rollback();
133+
}
134+
}
135+
136+
[Test]
137+
public void ConditionalInComplexGroupKeyFromSubqueryArrayAccess()
138+
{
139+
using (var session = OpenSession())
140+
using (var transaction = session.BeginTransaction())
141+
{
142+
var baseQuery = session.Query<TimeRecord>();
143+
144+
Assert.That(baseQuery.Sum(x => x.TimeInHours), Is.EqualTo(78));
145+
146+
var query = baseQuery.Select(t => new object[] { t })
147+
.SelectMany(t => ((TimeRecord)t[0]).Components.Select(c => (object)c.Id).DefaultIfEmpty().Select(c => new[] { t[0], c }))
148+
.GroupBy(j => new object[] { ((TimeRecord)j[0]).Project.Job.BillingType == BillingType.None ? 0 : 1, ((Component)j[1]).Name }, j => (TimeRecord)j[0])
149+
.Select(g => new object[] { g.Key, g.Count(), g.Sum(t => (decimal?)t.TimeInHours) });
150+
151+
var results = query.ToList().OrderBy(x => (int)((object[])x[0])[0]).ThenBy(x => (string)((object[])x[0])[1]);
152+
Assert.That(results.Select(x => x[1]), Is.EquivalentTo(new[] { 1, 2, 2, 2, 2, 2, 2, 2 }));
153+
Assert.That(results.Select(x => x[2]), Is.EquivalentTo(new[] { 1, 6, 7, 14, 14, 15, 22, 23 }));
154+
155+
Assert.That(results.Sum(x => (decimal?)x[2]), Is.EqualTo(102));
156+
157+
transaction.Rollback();
158+
}
159+
}
160+
}
161+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
3+
namespace="NHibernate.Test.NHSpecificTest.NH3844"
4+
assembly="NHibernate.Test">
5+
6+
<class name="Project">
7+
<id name="Id" column="ProjectId">
8+
<generator class="guid.comb"/>
9+
</id>
10+
<property name="Name" not-null="true"/>
11+
<bag name="Components" inverse="true" lazy="true" fetch="select">
12+
<key>
13+
<column name="ProjectId" not-null="true" />
14+
</key>
15+
<one-to-many class="Component" />
16+
</bag>
17+
<many-to-one name="Job" column="JobId" class="Job" not-null="true"/>
18+
</class>
19+
20+
<class name="Component">
21+
<id name="Id" column="ComponentId">
22+
<generator class="guid.comb"/>
23+
</id>
24+
<property name="Name" not-null="true"/>
25+
<many-to-one name="Project" column="ProjectId" class="Project" not-null="true"/>
26+
</class>
27+
28+
<class name="TimeRecord">
29+
<id name="Id" column="TimeRecordId">
30+
<generator class="guid.comb"/>
31+
</id>
32+
<property name="TimeInHours" not-null="true"/>
33+
<many-to-one name="Project" column="ProjectId" class="Project" />
34+
<bag name="Components" inverse="false" lazy="true" fetch="select">
35+
<key>
36+
<column name="TimeRecordId" not-null="true" />
37+
</key>
38+
<many-to-many class="Component">
39+
<column name="ComponentId" not-null="true" />
40+
</many-to-many>
41+
</bag>
42+
</class>
43+
44+
<class name="Job">
45+
<id name="Id" column="JobId">
46+
<generator class="guid.comb"/>
47+
</id>
48+
<property name="Name" not-null="true"/>
49+
<property name="BillingType" not-null="true" type="BillingType"/>
50+
</class>
51+
52+
</hibernate-mapping>

src/NHibernate.Test/NHibernate.Test.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -892,6 +892,8 @@
892892
<Compile Include="NHSpecificTest\NH3727\Entity.cs" />
893893
<Compile Include="NHSpecificTest\NH3727\FixtureByCode.cs" />
894894
<Compile Include="NHSpecificTest\NH3795\Fixture.cs" />
895+
<Compile Include="NHSpecificTest\NH3844\Domain.cs" />
896+
<Compile Include="NHSpecificTest\NH3844\Fixture.cs" />
895897
<Compile Include="NHSpecificTest\NH3818\Fixture.cs" />
896898
<Compile Include="NHSpecificTest\NH3818\MyLovelyCat.cs" />
897899
<Compile Include="NHSpecificTest\NH646\Domain.cs" />
@@ -3170,6 +3172,9 @@
31703172
<EmbeddedResource Include="NHSpecificTest\NH2218\Mappings.hbm.xml" />
31713173
<EmbeddedResource Include="NHSpecificTest\NH3046\Mappings.hbm.xml" />
31723174
<EmbeddedResource Include="NHSpecificTest\NH3518\Mappings.hbm.xml" />
3175+
<EmbeddedResource Include="NHSpecificTest\NH3844\Mappings.hbm.xml">
3176+
<SubType>Designer</SubType>
3177+
</EmbeddedResource>
31733178
<EmbeddedResource Include="NHSpecificTest\NH3609\Mappings.hbm.xml" />
31743179
<EmbeddedResource Include="NHSpecificTest\NH3818\Mappings.hbm.xml" />
31753180
<EmbeddedResource Include="NHSpecificTest\NH3666\Mappings.hbm.xml">

src/NHibernate/Linq/GroupBy/GroupKeyNominator.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Linq;
22
using System.Linq.Expressions;
33
using NHibernate.Linq.Expressions;
4+
using NHibernate.Linq.ReWriters;
45
using Remotion.Linq.Clauses.Expressions;
56
using Remotion.Linq.Clauses.ResultOperators;
67
using Remotion.Linq.Parsing;
@@ -70,5 +71,18 @@ protected override Expression VisitSubQueryExpression(SubQueryExpression express
7071
_requiresRootNomination = true;
7172
return base.VisitSubQueryExpression(expression);
7273
}
74+
75+
protected override Expression VisitBinaryExpression(BinaryExpression expression)
76+
{
77+
if (expression.NodeType != ExpressionType.ArrayIndex)
78+
return base.VisitBinaryExpression(expression);
79+
80+
// If we encounter an array index then we need to attempt to flatten it before nomination
81+
var flattenedExpression = new ArrayIndexExpressionFlattener().VisitExpression(expression);
82+
if (flattenedExpression != expression)
83+
return base.VisitExpression(flattenedExpression);
84+
85+
return base.VisitBinaryExpression(expression);
86+
}
7387
}
7488
}

0 commit comments

Comments
 (0)