Skip to content

Commit 4f5ed41

Browse files
committed
NH-3889 - Fixed the HqlSqlWalker grammar to handle subqueries without creating implicit joins
1 parent a6c78c3 commit 4f5ed41

File tree

5 files changed

+1796
-1484
lines changed

5 files changed

+1796
-1484
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace NHibernate.Test.NHSpecificTest.NH3889
5+
{
6+
public class TimeRecord
7+
{
8+
public virtual Guid Id { get; set; }
9+
public virtual Project Project { get; set; }
10+
public virtual Job ActualJob { get; set; }
11+
public virtual decimal Hours { get; set; }
12+
13+
public virtual TimeSetting Setting { get; set; }
14+
}
15+
16+
public class TimeSetting
17+
{
18+
public virtual Guid Id { get; set; }
19+
public virtual TimeInclude Include { get; set; }
20+
}
21+
22+
public class TimeInclude
23+
{
24+
public virtual Guid Id { get; set; }
25+
public virtual bool Flag { get; set; }
26+
}
27+
28+
public class Project
29+
{
30+
public virtual Guid Id { get; set; }
31+
public virtual string Name { get; set; }
32+
public virtual Job Job { get; set; }
33+
}
34+
35+
public class Job
36+
{
37+
public virtual Guid Id { get; set; }
38+
public virtual string Name { get; set; }
39+
}
40+
}
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
using System;
2+
using System.Linq;
3+
using NHibernate.Cfg.MappingSchema;
4+
using NHibernate.Linq;
5+
using NHibernate.Mapping.ByCode;
6+
using NUnit.Framework;
7+
8+
namespace NHibernate.Test.NHSpecificTest.NH3889
9+
{
10+
/// <summary>
11+
/// Fixture using 'by code' mappings
12+
/// </summary>
13+
/// <remarks>
14+
/// This fixture is identical to <see cref="Fixture" /> except the <see cref="Entity" /> mapping is performed
15+
/// by code in the GetMappings method, and does not require the <c>Mappings.hbm.xml</c> file. Use this approach
16+
/// if you prefer.
17+
/// </remarks>
18+
public class ByCodeFixture : TestCaseMappingByCode
19+
{
20+
protected override HbmMapping GetMappings()
21+
{
22+
var mapper = new ModelMapper();
23+
mapper.Class<Job>(rc =>
24+
{
25+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
26+
rc.Property(x => x.Name);
27+
});
28+
mapper.Class<Project>(rc =>
29+
{
30+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
31+
rc.Property(x => x.Name);
32+
rc.ManyToOne(x => x.Job, map =>
33+
{
34+
map.Column("JobId");
35+
map.NotNullable(true);
36+
});
37+
});
38+
mapper.Class<TimeRecord>(rc =>
39+
{
40+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
41+
rc.Property(x => x.Hours);
42+
rc.ManyToOne(x => x.Project, map =>
43+
{
44+
map.Column("ProjectId");
45+
map.NotNullable(true);
46+
});
47+
rc.ManyToOne(x => x.ActualJob, map =>
48+
{
49+
map.Column("ActualJobId");
50+
});
51+
rc.ManyToOne(x => x.Setting, map =>
52+
{
53+
map.Column("SettingId");
54+
});
55+
});
56+
57+
mapper.Class<TimeSetting>(rc =>
58+
{
59+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
60+
rc.ManyToOne(x => x.Include, map =>
61+
{
62+
map.Column("IncludeId");
63+
});
64+
});
65+
66+
mapper.Class<TimeInclude>(rc =>
67+
{
68+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
69+
rc.Property(x => x.Flag);
70+
});
71+
72+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
73+
}
74+
75+
protected override void OnSetUp()
76+
{
77+
using (ISession session = OpenSession())
78+
using (ITransaction transaction = session.BeginTransaction())
79+
{
80+
var job_a = new Job { Name = "Big Job" };
81+
session.Save(job_a);
82+
var job_b = new Job() { Name = "Small Job" };
83+
session.Save(job_b);
84+
85+
var project_a = new Project { Job = job_a, Name = "Big Job - Part A" };
86+
session.Save(project_a);
87+
var project_b = new Project { Job = job_a, Name = "Big Job - Part B" };
88+
session.Save(project_b);
89+
var project_c = new Project { Job = job_b, Name = "Small Job - Rework" };
90+
session.Save(project_c);
91+
92+
var include = new TimeInclude() { Flag = true };
93+
session.Save(include);
94+
var setting = new TimeSetting() { Include = include };
95+
session.Save(setting);
96+
97+
session.Save(new TimeRecord {Project = project_a, Hours = 2, Setting = setting }/*.AddTime(2)*/);
98+
session.Save(new TimeRecord {Project = project_a, Hours = 3, Setting = setting }/*.AddTime(3)*/);
99+
session.Save(new TimeRecord {Project = project_b, Hours = 5, Setting = setting }/*.AddTime(2).AddTime(3)*/);
100+
session.Save(new TimeRecord {Project = project_b, Hours = 2, Setting = setting }/*.AddTime(1).AddTime(1)*/);
101+
session.Save(new TimeRecord {Project = project_c, Hours = 7, Setting = setting }/*.AddTime(2).AddTime(3).AddTime(2)*/);
102+
session.Save(new TimeRecord {Project = project_c, ActualJob = job_a, Hours = 3, Setting = setting }/*.AddTime(1).AddTime(1).AddTime(1)*/);
103+
104+
session.Flush();
105+
transaction.Commit();
106+
}
107+
}
108+
109+
protected override void OnTearDown()
110+
{
111+
using (ISession session = OpenSession())
112+
using (ITransaction transaction = session.BeginTransaction())
113+
{
114+
session.Delete("from TimeRecord");
115+
session.Delete("from TimeInclude");
116+
session.Delete("from TimeSetting");
117+
session.Delete("from Project");
118+
session.Delete("from Job");
119+
120+
session.Flush();
121+
transaction.Commit();
122+
}
123+
}
124+
125+
[Test]
126+
public void CoalesceOnEntitySum()
127+
{
128+
using (ISession session = OpenSession())
129+
using (session.BeginTransaction())
130+
{
131+
var job_a = session.Query<Job>().Single(j => j.Name == "Big Job");
132+
var job_a_hours = session.Query<TimeRecord>()
133+
.Where(t => (t.ActualJob ?? t.Project.Job) == job_a)
134+
.Sum(t => t.Hours);
135+
Assert.That(job_a_hours, Is.EqualTo(15));
136+
137+
var job_b = session.Query<Job>().Single(j => j.Name == "Small Job");
138+
var job_b_hours = session.Query<TimeRecord>()
139+
.Where(t => (t.ActualJob ?? t.Project.Job) == job_b)
140+
.Sum(t => t.Hours);
141+
Assert.That(job_b_hours, Is.EqualTo(7));
142+
}
143+
}
144+
145+
[Test]
146+
public void CoalesceOnEntitySumWithExtraJoin()
147+
{
148+
using (ISession session = OpenSession())
149+
using (session.BeginTransaction())
150+
{
151+
var include = session.Query<TimeInclude>().Single();
152+
153+
var job_a = session.Query<Job>().Single(j => j.Name == "Big Job");
154+
var job_a_hours = session.Query<TimeRecord>()
155+
.Where(t => (t.ActualJob ?? t.Project.Job) == job_a)
156+
.Where(t => t.Setting.Include == include)
157+
.Sum(t => t.Hours);
158+
Assert.That(job_a_hours, Is.EqualTo(15));
159+
160+
var job_b = session.Query<Job>().Single(j => j.Name == "Small Job");
161+
var job_b_hours = session.Query<TimeRecord>()
162+
.Where(t => (t.ActualJob ?? t.Project.Job) == job_b)
163+
.Where(t => t.Setting.Include == include)
164+
.Sum(t => t.Hours);
165+
Assert.That(job_b_hours, Is.EqualTo(7));
166+
}
167+
}
168+
169+
[Test]
170+
public void CoalesceOnEntitySubselectSum()
171+
{
172+
using (ISession session = OpenSession())
173+
using (session.BeginTransaction())
174+
{
175+
var query = session.Query<Job>()
176+
.Select(j => new
177+
{
178+
Job = j,
179+
Hours = session.Query<TimeRecord>()
180+
.Where(t => (t.ActualJob ?? t.Project.Job) == j)
181+
.Sum(t => (decimal?)t.Hours) ?? 0
182+
});
183+
var results = query.ToList();
184+
185+
Assert.That(results.Count, Is.EqualTo(2));
186+
Assert.That(results.Single(x => x.Job.Name == "Big Job").Hours, Is.EqualTo(15));
187+
Assert.That(results.Single(x => x.Job.Name == "Small Job").Hours, Is.EqualTo(7));
188+
}
189+
}
190+
191+
[Test]
192+
public void CoalesceOnEntitySubselectSumWithExtraJoin()
193+
{
194+
using (ISession session = OpenSession())
195+
using (session.BeginTransaction())
196+
{
197+
var include = session.Query<TimeInclude>().Single();
198+
199+
var query = session.Query<Job>()
200+
.Select(j => new
201+
{
202+
Job = j,
203+
Hours = session.Query<TimeRecord>()
204+
.Where(t => (t.ActualJob ?? t.Project.Job) == j)
205+
.Where(t => t.Setting.Include == include)
206+
.Sum(t => (decimal?)t.Hours) ?? 0
207+
});
208+
var results = query.ToList();
209+
210+
Assert.That(results.Count, Is.EqualTo(2));
211+
Assert.That(results.Single(x => x.Job.Name == "Big Job").Hours, Is.EqualTo(15));
212+
Assert.That(results.Single(x => x.Job.Name == "Small Job").Hours, Is.EqualTo(7));
213+
}
214+
}
215+
216+
[Test]
217+
public void CoalesceOnIdSubselectSum()
218+
{
219+
using (ISession session = OpenSession())
220+
using (session.BeginTransaction())
221+
{
222+
var query = session.Query<Job>()
223+
.Select(j => new
224+
{
225+
Job = j,
226+
Hours = session.Query<TimeRecord>()
227+
.Where(t => ((Guid?)t.ActualJob.Id ?? t.Project.Job.Id) == j.Id)
228+
.Sum(t => (decimal?)t.Hours) ?? 0
229+
});
230+
var results = query.ToList();
231+
232+
Assert.That(results.Count, Is.EqualTo(2));
233+
Assert.That(results.Single(x => x.Job.Name == "Big Job").Hours, Is.EqualTo(15));
234+
Assert.That(results.Single(x => x.Job.Name == "Small Job").Hours, Is.EqualTo(7));
235+
}
236+
}
237+
238+
[Test]
239+
public void CoalesceOnIdSubselectSumWithExtraJoin()
240+
{
241+
using (ISession session = OpenSession())
242+
using (session.BeginTransaction())
243+
{
244+
var include = session.Query<TimeInclude>().Single();
245+
246+
var query = session.Query<Job>()
247+
.Select(j => new
248+
{
249+
Job = j,
250+
Hours = session.Query<TimeRecord>()
251+
.Where(t => ((Guid?)t.ActualJob.Id ?? t.Project.Job.Id) == j.Id)
252+
.Where(t => t.Setting.Include == include)
253+
.Sum(t => (decimal?)t.Hours) ?? 0
254+
});
255+
var results = query.ToList();
256+
257+
Assert.That(results.Count, Is.EqualTo(2));
258+
Assert.That(results.Single(x => x.Job.Name == "Big Job").Hours, Is.EqualTo(15));
259+
Assert.That(results.Single(x => x.Job.Name == "Small Job").Hours, Is.EqualTo(7));
260+
}
261+
}
262+
}
263+
}

src/NHibernate.Test/NHibernate.Test.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,8 @@
723723
<Compile Include="NHSpecificTest\BagWithLazyExtraAndFilter\Fixture.cs" />
724724
<Compile Include="Linq\ByMethod\DistinctTests.cs" />
725725
<Compile Include="Component\Basic\ComponentWithUniqueConstraintTests.cs" />
726+
<Compile Include="NHSpecificTest\NH3889\Entity.cs" />
727+
<Compile Include="NHSpecificTest\NH3889\FixtureByCode.cs" />
726728
<Compile Include="NHSpecificTest\NH3414\Entity.cs" />
727729
<Compile Include="NHSpecificTest\NH3414\FixtureByCode.cs" />
728730
<Compile Include="NHSpecificTest\NH2218\Fixture.cs" />
@@ -3754,7 +3756,6 @@
37543756
<Folder Include="Properties\" />
37553757
</ItemGroup>
37563758
<ItemGroup>
3757-
37583759
</ItemGroup>
37593760
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
37603761
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.

0 commit comments

Comments
 (0)