diff --git a/src/NHibernate.Test/Async/Linq/ConstantTest.cs b/src/NHibernate.Test/Async/Linq/ConstantTest.cs index 718ac40fdc2..bc8c107dc39 100644 --- a/src/NHibernate.Test/Async/Linq/ConstantTest.cs +++ b/src/NHibernate.Test/Async/Linq/ConstantTest.cs @@ -11,12 +11,13 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using NHibernate.Criterion; using NHibernate.DomainModel.Northwind.Entities; using NHibernate.Engine.Query; +using NHibernate.Linq; using NHibernate.Linq.Visitors; using NHibernate.Util; using NUnit.Framework; -using NHibernate.Linq; namespace NHibernate.Test.Linq { @@ -269,5 +270,26 @@ public async Task PlansWithNonParameterizedConstantsAreNotCachedAsync() Has.Count.EqualTo(0), "Query plan should not be cached."); } + + [Test] + public async Task PlansWithNonParameterizedConstantsAreNotCachedForExpandedQueryAsync() + { + var queryPlanCacheType = typeof(QueryPlanCache); + + var cache = (SoftLimitMRUCache) + queryPlanCacheType + .GetField("planCache", BindingFlags.Instance | BindingFlags.NonPublic) + .GetValue(Sfi.QueryPlanCache); + cache.Clear(); + + var ids = new[] {"ANATR", "UNKNOWN"}.ToList(); + await (db.Customers.Where(x => ids.Contains(x.CustomerId)).Select( + c => new {c.CustomerId, c.ContactName, Constant = 1}).FirstAsync()); + + Assert.That( + cache, + Has.Count.EqualTo(0), + "Query plan should not be cached."); + } } } diff --git a/src/NHibernate.Test/Linq/ConstantTest.cs b/src/NHibernate.Test/Linq/ConstantTest.cs index 2b4be96a912..d677d4ae4c6 100644 --- a/src/NHibernate.Test/Linq/ConstantTest.cs +++ b/src/NHibernate.Test/Linq/ConstantTest.cs @@ -1,8 +1,10 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using NHibernate.Criterion; using NHibernate.DomainModel.Northwind.Entities; using NHibernate.Engine.Query; +using NHibernate.Linq; using NHibernate.Linq.Visitors; using NHibernate.Util; using NUnit.Framework; @@ -276,5 +278,62 @@ public void PlansWithNonParameterizedConstantsAreNotCached() Has.Count.EqualTo(0), "Query plan should not be cached."); } + + [Test] + public void PlansWithNonParameterizedConstantsAreNotCachedForExpandedQuery() + { + var queryPlanCacheType = typeof(QueryPlanCache); + + var cache = (SoftLimitMRUCache) + queryPlanCacheType + .GetField("planCache", BindingFlags.Instance | BindingFlags.NonPublic) + .GetValue(Sfi.QueryPlanCache); + cache.Clear(); + + var ids = new[] {"ANATR", "UNKNOWN"}.ToList(); + db.Customers.Where(x => ids.Contains(x.CustomerId)).Select( + c => new {c.CustomerId, c.ContactName, Constant = 1}).First(); + + Assert.That( + cache, + Has.Count.EqualTo(0), + "Query plan should not be cached."); + } + + //GH-2298 - Different Update queries - same query cache plan + [Test] + public void DmlPlansForExpandedQuery() + { + var queryPlanCacheType = typeof(QueryPlanCache); + + var cache = (SoftLimitMRUCache) + queryPlanCacheType + .GetField("planCache", BindingFlags.Instance | BindingFlags.NonPublic) + .GetValue(Sfi.QueryPlanCache); + cache.Clear(); + + using (session.BeginTransaction()) + { + var list = new[] {"UNKNOWN", "UNKNOWN2"}.ToList(); + db.Customers.Where(x => list.Contains(x.CustomerId)).Update( + x => new Customer + { + CompanyName = "Constant1" + }); + + db.Customers.Where(x => list.Contains(x.CustomerId)) + .Update( + x => new Customer + { + ContactName = "Constant1" + }); + + Assert.That( + cache.Count, + //2 original queries + 2 expanded queries are expected in cache + Is.EqualTo(0).Or.EqualTo(4), + "Query plans should either be cached separately or not cached at all."); + } + } } } diff --git a/src/NHibernate/Engine/Query/QueryPlanCache.cs b/src/NHibernate/Engine/Query/QueryPlanCache.cs index 6a0c0b6a7ee..4964b9014e7 100644 --- a/src/NHibernate/Engine/Query/QueryPlanCache.cs +++ b/src/NHibernate/Engine/Query/QueryPlanCache.cs @@ -62,7 +62,7 @@ public IQueryExpressionPlan GetHQLQueryPlan(IQueryExpression queryExpression, bo } plan = new QueryExpressionPlan(queryExpression, shallow, enabledFilters, factory); // 6.0 TODO: add "CanCachePlan { get; }" to IQueryExpression interface - if (!(queryExpression is NhLinqExpression linqExpression) || linqExpression.CanCachePlan) + if (!(queryExpression is ICacheableQueryExpression linqExpression) || linqExpression.CanCachePlan) planCache.Put(key, plan); else log.Debug("Query plan not cacheable"); @@ -117,7 +117,7 @@ public IQueryExpressionPlan GetFilterQueryPlan(IQueryExpression queryExpression, log.Debug("unable to locate collection-filter query plan in cache; generating ({0} : {1})", collectionRole, queryExpression.Key); plan = new FilterQueryPlan(queryExpression, collectionRole, shallow, enabledFilters, factory); // 6.0 TODO: add "CanCachePlan { get; }" to IQueryExpression interface - if (!(queryExpression is NhLinqExpression linqExpression) || linqExpression.CanCachePlan) + if (!(queryExpression is ICacheableQueryExpression linqExpression) || linqExpression.CanCachePlan) planCache.Put(key, plan); else log.Debug("Query plan not cacheable"); diff --git a/src/NHibernate/IQueryExpression.cs b/src/NHibernate/IQueryExpression.cs index c5ed085c7d8..65bbe121f68 100755 --- a/src/NHibernate/IQueryExpression.cs +++ b/src/NHibernate/IQueryExpression.cs @@ -5,6 +5,12 @@ namespace NHibernate { + //TODO 6.0: Merge into IQueryExpression + internal interface ICacheableQueryExpression + { + bool CanCachePlan { get; } + } + public interface IQueryExpression { IASTNode Translate(ISessionFactoryImplementor sessionFactory, bool filter); @@ -12,4 +18,4 @@ public interface IQueryExpression System.Type Type { get; } IList ParameterDescriptors { get; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Impl/ExpressionQueryImpl.cs b/src/NHibernate/Impl/ExpressionQueryImpl.cs index 60683977210..95a106f09c3 100644 --- a/src/NHibernate/Impl/ExpressionQueryImpl.cs +++ b/src/NHibernate/Impl/ExpressionQueryImpl.cs @@ -150,9 +150,10 @@ public override object[] ValueArray() } } - internal class ExpandedQueryExpression : IQueryExpression + internal class ExpandedQueryExpression : IQueryExpression, ICacheableQueryExpression { private readonly IASTNode _tree; + private ICacheableQueryExpression _cacheableExpression; public ExpandedQueryExpression(IQueryExpression queryExpression, IASTNode tree, string key) { @@ -160,6 +161,7 @@ public ExpandedQueryExpression(IQueryExpression queryExpression, IASTNode tree, Key = key; Type = queryExpression.Type; ParameterDescriptors = queryExpression.ParameterDescriptors; + _cacheableExpression = queryExpression as ICacheableQueryExpression; } #region IQueryExpression Members @@ -176,6 +178,8 @@ public IASTNode Translate(ISessionFactoryImplementor sessionFactory, bool filter public IList ParameterDescriptors { get; private set; } #endregion + + public bool CanCachePlan => _cacheableExpression?.CanCachePlan ?? true; } internal class ParameterExpander diff --git a/src/NHibernate/Linq/NhLinqExpression.cs b/src/NHibernate/Linq/NhLinqExpression.cs index 50ec325c47f..2a00840949a 100644 --- a/src/NHibernate/Linq/NhLinqExpression.cs +++ b/src/NHibernate/Linq/NhLinqExpression.cs @@ -11,7 +11,7 @@ namespace NHibernate.Linq { - public class NhLinqExpression : IQueryExpression + public class NhLinqExpression : IQueryExpression, ICacheableQueryExpression { public string Key { get; protected set; }