Skip to content

Commit 90f5234

Browse files
PleasantDhazzik
authored andcommitted
NH-3889 - Fixed the HqlSqlWalker grammar to handle subqueries without creating implicit joins
1 parent bfeaaf1 commit 90f5234

File tree

4 files changed

+313
-2
lines changed

4 files changed

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

src/NHibernate.Test/NHibernate.Test.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,6 +742,8 @@
742742
<Compile Include="NHSpecificTest\NH3912\ReusableBatcherFixture.cs" />
743743
<Compile Include="NHSpecificTest\NH3918\Model.cs" />
744744
<Compile Include="NHSpecificTest\NH3918\FixtureByCode.cs" />
745+
<Compile Include="NHSpecificTest\NH3889\Entity.cs" />
746+
<Compile Include="NHSpecificTest\NH3889\FixtureByCode.cs" />
745747
<Compile Include="NHSpecificTest\NH3414\Entity.cs" />
746748
<Compile Include="NHSpecificTest\NH3414\FixtureByCode.cs" />
747749
<Compile Include="NHSpecificTest\NH2218\Fixture.cs" />

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,11 +123,16 @@ query
123123
// The query / subquery rule. Pops the current 'from node' context
124124
// (list of aliases).
125125
unionedQuery!
126+
@init{
127+
bool oldInSelect = _inSelect;
128+
_inSelect = false;
129+
}
126130
@after {
127131
// Antlr note: #x_in refers to the input AST, #x refers to the output AST
128132
BeforeStatementCompletion( "select" );
129133
ProcessQuery( $s.tree, $unionedQuery.tree );
130134
AfterStatementCompletion( "select" );
135+
_inSelect = oldInSelect;
131136
}
132137
: ^( QUERY { BeforeStatement( "select", SELECT ); }
133138
// The first phase places the FROM first to make processing the SELECT simpler.
@@ -189,11 +194,10 @@ selectClause!
189194
;
190195
191196
selectExprList @init{
192-
bool oldInSelect = _inSelect;
193197
_inSelect = true;
194198
}
195199
: ( selectExpr | aliasedSelectExpr )+ {
196-
_inSelect = oldInSelect;
200+
_inSelect = false;
197201
}
198202
;
199203

0 commit comments

Comments
 (0)