Skip to content

Commit 2a4041e

Browse files
EamonHethertonhazzik
authored andcommitted
Fix for NH-3050
Added constructor overload to HQLQueryPlanKey that allows itto discriminate between different IQueryExpression type. In particular NhLinqExpression vs ExpandedQueryExpression.
1 parent aeb74f2 commit 2a4041e

File tree

4 files changed

+167
-6
lines changed

4 files changed

+167
-6
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System;
2+
3+
namespace NHibernate.Test.NHSpecificTest.NH3050
4+
{
5+
class Entity
6+
{
7+
public virtual Guid Id { get; set; }
8+
public virtual string Name { get; set; }
9+
}
10+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
using System.Linq;
2+
using NHibernate.Cfg.MappingSchema;
3+
using NHibernate.Linq;
4+
using NHibernate.Mapping.ByCode;
5+
using NUnit.Framework;
6+
using System.Collections.Generic;
7+
using System;
8+
9+
namespace NHibernate.Test.NHSpecificTest.NH3050
10+
{
11+
/// <summary>
12+
/// Fixture using 'by code' mappings
13+
/// </summary>
14+
/// <remarks>
15+
/// This fixture is identical to <see cref="Fixture" /> except the <see cref="Entity" /> mapping is performed
16+
/// by code in the GetMappings method, and does not require the <c>Mappings.hbm.xml</c> file. Use this approach
17+
/// if you prefer.
18+
/// </remarks>
19+
[TestFixture]
20+
public class FixtureByCode : TestCaseMappingByCode
21+
{
22+
protected override HbmMapping GetMappings()
23+
{
24+
var mapper = new ModelMapper();
25+
mapper.Class<Entity>(rc =>
26+
{
27+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
28+
rc.Property(x => x.Name);
29+
});
30+
31+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
32+
}
33+
34+
protected override void OnSetUp()
35+
{
36+
using (ISession session = OpenSession())
37+
using (ITransaction transaction = session.BeginTransaction())
38+
{
39+
var e1 = new Entity { Name = "Bob" };
40+
session.Save(e1);
41+
42+
var e2 = new Entity { Name = "Sally" };
43+
session.Save(e2);
44+
45+
session.Flush();
46+
transaction.Commit();
47+
}
48+
}
49+
50+
protected override void OnTearDown()
51+
{
52+
using (ISession session = OpenSession())
53+
using (ITransaction transaction = session.BeginTransaction())
54+
{
55+
session.Delete("from System.Object");
56+
57+
session.Flush();
58+
transaction.Commit();
59+
}
60+
}
61+
62+
[Test]
63+
public void NH3050_Reproduction()
64+
{
65+
//firstly to make things simpler, we set the query plan cache size to 1
66+
Assert.IsTrue(TrySetQueryPlanCacheSize(Sfi, 1));
67+
68+
using (ISession session = OpenSession())
69+
using (session.BeginTransaction())
70+
{
71+
var names = new List<string>() { "Bob" };
72+
var query = from e in session.Query<Entity>()
73+
where names.Contains(e.Name)
74+
select e;
75+
76+
//create a future, which will prepare a linq query plan and add it to the cache (NhLinqExpression)
77+
var future = query.ToFuture();
78+
79+
//we need enough unique queries (different to our main query here) to fill the plan cache so that our previous plan is evicted
80+
//in this case we only need one as we have limited the cache size to 1
81+
(from e in session.Query<Entity>()
82+
where e.Name == ""
83+
select e).ToList();
84+
85+
//garbage collection runs so that the query plan for our future which is a weak reference now in the plan cache is collected.
86+
GC.Collect();
87+
88+
//execute future which creates an ExpandedQueryExpression and adds it to the plan cache (generates the same cache plan key as the NhLinqExpression)
89+
future.ToList();
90+
91+
//execute original query again which will look for a NhLinqExpression in the plan cache but because it has already been evicted
92+
//and because the ExpandedQueryExpression generates the same cache key, the ExpandedQueryExpression is returned and
93+
//an exception is thrown as it tries to cast to a NhLinqExpression.
94+
query.ToList();
95+
}
96+
}
97+
98+
/// <summary>
99+
/// Uses reflection to create a new SoftLimitMRUCache with a specified size and sets session factory query plan chache to it.
100+
/// This is done like this as NHibernate does not currently provide any way to specify the query plan cache size through configuration.
101+
/// </summary>
102+
/// <param name="factory"></param>
103+
/// <param name="size"></param>
104+
/// <returns></returns>
105+
private static bool TrySetQueryPlanCacheSize(NHibernate.ISessionFactory factory, int size)
106+
{
107+
var factoryImpl = factory as NHibernate.Impl.SessionFactoryImpl;
108+
if (factoryImpl != null)
109+
{
110+
var queryPlanCacheFieldInfo = typeof(NHibernate.Impl.SessionFactoryImpl).GetField("queryPlanCache", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
111+
if (queryPlanCacheFieldInfo != null)
112+
{
113+
var queryPlanCache = (NHibernate.Engine.Query.QueryPlanCache)queryPlanCacheFieldInfo.GetValue(factoryImpl);
114+
115+
var planCacheFieldInfo = typeof(NHibernate.Engine.Query.QueryPlanCache).GetField("planCache", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
116+
if (planCacheFieldInfo != null)
117+
{
118+
var softLimitMRUCache = new NHibernate.Util.SoftLimitMRUCache(size);
119+
120+
planCacheFieldInfo.SetValue(queryPlanCache, softLimitMRUCache);
121+
return true;
122+
}
123+
}
124+
}
125+
return false;
126+
}
127+
}
128+
}

src/NHibernate.Test/NHibernate.Test.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,8 @@
672672
<Compile Include="NHSpecificTest\NH2033\Fixture.cs" />
673673
<Compile Include="NHSpecificTest\NH2819\Entities.cs" />
674674
<Compile Include="NHSpecificTest\NH2819\Fixture.cs" />
675+
<Compile Include="NHSpecificTest\NH3050\Entity.cs" />
676+
<Compile Include="NHSpecificTest\NH3050\FixtureByCode.cs" />
675677
<Compile Include="NHSpecificTest\NH3093\Domain.cs" />
676678
<Compile Include="NHSpecificTest\NH3093\Fixture.cs" />
677679
<Compile Include="NHSpecificTest\NH2806\Domain.cs" />

src/NHibernate/Engine/Query/QueryPlanCache.cs

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public IQueryExpressionPlan GetHQLQueryPlan(IQueryExpression queryExpression, bo
7474
{
7575
string expressionStr = queryExpression.Key;
7676

77-
var key = new HQLQueryPlanKey(expressionStr, shallow, enabledFilters);
77+
var key = new HQLQueryPlanKey(queryExpression, shallow, enabledFilters);
7878
var plan = (IQueryExpressionPlan)planCache[key];
7979

8080
if (plan == null)
@@ -173,15 +173,27 @@ private ParameterMetadata BuildNativeSQLParameterMetadata(string sqlString)
173173
}
174174

175175
[Serializable]
176-
private class HQLQueryPlanKey
176+
private class HQLQueryPlanKey : IEquatable<HQLQueryPlanKey>
177177
{
178178
private readonly string query;
179179
private readonly bool shallow;
180180
private readonly HashSet<string> filterNames;
181181
private readonly int hashCode;
182+
private readonly System.Type queryTypeDiscriminator;
182183

183184
public HQLQueryPlanKey(string query, bool shallow, IDictionary<string, IFilter> enabledFilters)
185+
: this(typeof(object), query, shallow, enabledFilters)
184186
{
187+
}
188+
189+
public HQLQueryPlanKey(IQueryExpression queryExpression, bool shallow, IDictionary<string, IFilter> enabledFilters)
190+
: this(queryExpression.GetType(), queryExpression.Key, shallow, enabledFilters)
191+
{
192+
}
193+
194+
protected HQLQueryPlanKey(System.Type queryTypeDiscriminator, string query, bool shallow, IDictionary<string, IFilter> enabledFilters)
195+
{
196+
this.queryTypeDiscriminator = queryTypeDiscriminator;
185197
this.query = query;
186198
this.shallow = shallow;
187199

@@ -194,10 +206,14 @@ public HQLQueryPlanKey(string query, bool shallow, IDictionary<string, IFilter>
194206
filterNames = new HashSet<string>(enabledFilters.Keys);
195207
}
196208

197-
int hash = query.GetHashCode();
198-
hash = 29 * hash + (shallow ? 1 : 0);
199-
hash = 29 * hash + CollectionHelper.GetHashCode(filterNames);
200-
hashCode = hash;
209+
unchecked
210+
{
211+
var hash = query.GetHashCode();
212+
hash = 29*hash + (shallow ? 1 : 0);
213+
hash = 29*hash + CollectionHelper.GetHashCode(filterNames);
214+
hash = 29*hash + queryTypeDiscriminator.GetHashCode();
215+
hashCode = hash;
216+
}
201217
}
202218

203219
public override bool Equals(object obj)
@@ -227,6 +243,11 @@ public bool Equals(HQLQueryPlanKey that)
227243
return false;
228244
}
229245

246+
if (queryTypeDiscriminator != that.queryTypeDiscriminator)
247+
{
248+
return false;
249+
}
250+
230251
return true;
231252
}
232253

0 commit comments

Comments
 (0)