diff --git a/appveyor.yml b/appveyor.yml index cf1c860e247..38d1127ea29 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 5.1.6.{build} +version: 5.1.7.{build} image: Visual Studio 2017 environment: matrix: diff --git a/build-common/NHibernate.props b/build-common/NHibernate.props index 2c4de1abb7c..ff667d6a2e1 100644 --- a/build-common/NHibernate.props +++ b/build-common/NHibernate.props @@ -2,7 +2,7 @@ 5 1 - 6 + 7 $(VersionMajor).$(VersionMinor).$(VersionPatch) diff --git a/build-common/common.xml b/build-common/common.xml index 8091aeb596b..80c218a12ff 100644 --- a/build-common/common.xml +++ b/build-common/common.xml @@ -13,8 +13,8 @@ - - + + diff --git a/releasenotes.txt b/releasenotes.txt index c3d372ed4bd..5d1582a3211 100644 --- a/releasenotes.txt +++ b/releasenotes.txt @@ -1,3 +1,11 @@ +Build 5.1.7 +============================= + +Release notes - NHibernate - Version 5.1.7 + +** Bug + * #2298 Dml Linq Update Produce Wrong Sql + Build 5.1.6 ============================= 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 9b817c5eb87..521e538d7bc 100644 --- a/src/NHibernate/Engine/Query/QueryPlanCache.cs +++ b/src/NHibernate/Engine/Query/QueryPlanCache.cs @@ -60,7 +60,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"); @@ -115,7 +115,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 b27380eb7f5..f0e4d8e8ef9 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; }