Skip to content

Commit e089346

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

File tree

5 files changed

+1818
-1484
lines changed

5 files changed

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

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)