Skip to content

Commit fb83c8b

Browse files
bahusoidfredericDelaporte
authored andcommitted
Do not cache ExpandedQueryExpression query plan if it's based on not cacheable query expression
1 parent 72fddfb commit fb83c8b

File tree

6 files changed

+97
-6
lines changed

6 files changed

+97
-6
lines changed

src/NHibernate.Test/Async/Linq/ConstantTest.cs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@
1111
using System.Collections.Generic;
1212
using System.Linq;
1313
using System.Reflection;
14+
using NHibernate.Criterion;
1415
using NHibernate.DomainModel.Northwind.Entities;
1516
using NHibernate.Engine.Query;
17+
using NHibernate.Linq;
1618
using NHibernate.Linq.Visitors;
1719
using NHibernate.Util;
1820
using NUnit.Framework;
19-
using NHibernate.Linq;
2021

2122
namespace NHibernate.Test.Linq
2223
{
@@ -269,5 +270,26 @@ public async Task PlansWithNonParameterizedConstantsAreNotCachedAsync()
269270
Has.Count.EqualTo(0),
270271
"Query plan should not be cached.");
271272
}
273+
274+
[Test]
275+
public async Task PlansWithNonParameterizedConstantsAreNotCachedForExpandedQueryAsync()
276+
{
277+
var queryPlanCacheType = typeof(QueryPlanCache);
278+
279+
var cache = (SoftLimitMRUCache)
280+
queryPlanCacheType
281+
.GetField("planCache", BindingFlags.Instance | BindingFlags.NonPublic)
282+
.GetValue(Sfi.QueryPlanCache);
283+
cache.Clear();
284+
285+
var ids = new[] {"ANATR", "UNKNOWN"}.ToList();
286+
await (db.Customers.Where(x => ids.Contains(x.CustomerId)).Select(
287+
c => new {c.CustomerId, c.ContactName, Constant = 1}).FirstAsync());
288+
289+
Assert.That(
290+
cache,
291+
Has.Count.EqualTo(0),
292+
"Query plan should not be cached.");
293+
}
272294
}
273295
}

src/NHibernate.Test/Linq/ConstantTest.cs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
using System.Collections.Generic;
22
using System.Linq;
33
using System.Reflection;
4+
using NHibernate.Criterion;
45
using NHibernate.DomainModel.Northwind.Entities;
56
using NHibernate.Engine.Query;
7+
using NHibernate.Linq;
68
using NHibernate.Linq.Visitors;
79
using NHibernate.Util;
810
using NUnit.Framework;
@@ -276,5 +278,62 @@ public void PlansWithNonParameterizedConstantsAreNotCached()
276278
Has.Count.EqualTo(0),
277279
"Query plan should not be cached.");
278280
}
281+
282+
[Test]
283+
public void PlansWithNonParameterizedConstantsAreNotCachedForExpandedQuery()
284+
{
285+
var queryPlanCacheType = typeof(QueryPlanCache);
286+
287+
var cache = (SoftLimitMRUCache)
288+
queryPlanCacheType
289+
.GetField("planCache", BindingFlags.Instance | BindingFlags.NonPublic)
290+
.GetValue(Sfi.QueryPlanCache);
291+
cache.Clear();
292+
293+
var ids = new[] {"ANATR", "UNKNOWN"}.ToList();
294+
db.Customers.Where(x => ids.Contains(x.CustomerId)).Select(
295+
c => new {c.CustomerId, c.ContactName, Constant = 1}).First();
296+
297+
Assert.That(
298+
cache,
299+
Has.Count.EqualTo(0),
300+
"Query plan should not be cached.");
301+
}
302+
303+
//GH-2298 - Different Update queries - same query cache plan
304+
[Test]
305+
public void DmlPlansForExpandedQuery()
306+
{
307+
var queryPlanCacheType = typeof(QueryPlanCache);
308+
309+
var cache = (SoftLimitMRUCache)
310+
queryPlanCacheType
311+
.GetField("planCache", BindingFlags.Instance | BindingFlags.NonPublic)
312+
.GetValue(Sfi.QueryPlanCache);
313+
cache.Clear();
314+
315+
using (session.BeginTransaction())
316+
{
317+
var list = new[] {"UNKNOWN", "UNKNOWN2"}.ToList();
318+
db.Customers.Where(x => list.Contains(x.CustomerId)).Update(
319+
x => new Customer
320+
{
321+
CompanyName = "Constant1"
322+
});
323+
324+
db.Customers.Where(x => list.Contains(x.CustomerId))
325+
.Update(
326+
x => new Customer
327+
{
328+
ContactName = "Constant1"
329+
});
330+
331+
Assert.That(
332+
cache.Count,
333+
//2 original queries + 2 expanded queries are expected in cache
334+
Is.EqualTo(0).Or.EqualTo(4),
335+
"Query plans should either be cached separately or not cached at all.");
336+
}
337+
}
279338
}
280339
}

src/NHibernate/Engine/Query/QueryPlanCache.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public IQueryExpressionPlan GetHQLQueryPlan(IQueryExpression queryExpression, bo
6262
}
6363
plan = new QueryExpressionPlan(queryExpression, shallow, enabledFilters, factory);
6464
// 6.0 TODO: add "CanCachePlan { get; }" to IQueryExpression interface
65-
if (!(queryExpression is NhLinqExpression linqExpression) || linqExpression.CanCachePlan)
65+
if (!(queryExpression is ICacheableQueryExpression linqExpression) || linqExpression.CanCachePlan)
6666
planCache.Put(key, plan);
6767
else
6868
log.Debug("Query plan not cacheable");
@@ -117,7 +117,7 @@ public IQueryExpressionPlan GetFilterQueryPlan(IQueryExpression queryExpression,
117117
log.Debug("unable to locate collection-filter query plan in cache; generating ({0} : {1})", collectionRole, queryExpression.Key);
118118
plan = new FilterQueryPlan(queryExpression, collectionRole, shallow, enabledFilters, factory);
119119
// 6.0 TODO: add "CanCachePlan { get; }" to IQueryExpression interface
120-
if (!(queryExpression is NhLinqExpression linqExpression) || linqExpression.CanCachePlan)
120+
if (!(queryExpression is ICacheableQueryExpression linqExpression) || linqExpression.CanCachePlan)
121121
planCache.Put(key, plan);
122122
else
123123
log.Debug("Query plan not cacheable");

src/NHibernate/IQueryExpression.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,17 @@
55

66
namespace NHibernate
77
{
8+
//TODO 6.0: Merge into IQueryExpression
9+
internal interface ICacheableQueryExpression
10+
{
11+
bool CanCachePlan { get; }
12+
}
13+
814
public interface IQueryExpression
915
{
1016
IASTNode Translate(ISessionFactoryImplementor sessionFactory, bool filter);
1117
string Key { get; }
1218
System.Type Type { get; }
1319
IList<NamedParameterDescriptor> ParameterDescriptors { get; }
1420
}
15-
}
21+
}

src/NHibernate/Impl/ExpressionQueryImpl.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,16 +150,18 @@ public override object[] ValueArray()
150150
}
151151
}
152152

153-
internal class ExpandedQueryExpression : IQueryExpression
153+
internal class ExpandedQueryExpression : IQueryExpression, ICacheableQueryExpression
154154
{
155155
private readonly IASTNode _tree;
156+
private ICacheableQueryExpression _cacheableExpression;
156157

157158
public ExpandedQueryExpression(IQueryExpression queryExpression, IASTNode tree, string key)
158159
{
159160
_tree = tree;
160161
Key = key;
161162
Type = queryExpression.Type;
162163
ParameterDescriptors = queryExpression.ParameterDescriptors;
164+
_cacheableExpression = queryExpression as ICacheableQueryExpression;
163165
}
164166

165167
#region IQueryExpression Members
@@ -176,6 +178,8 @@ public IASTNode Translate(ISessionFactoryImplementor sessionFactory, bool filter
176178
public IList<NamedParameterDescriptor> ParameterDescriptors { get; private set; }
177179

178180
#endregion
181+
182+
public bool CanCachePlan => _cacheableExpression?.CanCachePlan ?? true;
179183
}
180184

181185
internal class ParameterExpander

src/NHibernate/Linq/NhLinqExpression.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
namespace NHibernate.Linq
1313
{
14-
public class NhLinqExpression : IQueryExpression
14+
public class NhLinqExpression : IQueryExpression, ICacheableQueryExpression
1515
{
1616
public string Key { get; protected set; }
1717

0 commit comments

Comments
 (0)