From 2662dc3029d6ea00786f235d78a66b9bb9e3b0c4 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Wed, 25 Sep 2019 20:19:01 +0300 Subject: [PATCH 1/3] Cache Dml queries --- .../Async/Linq/ConstantTest.cs | 32 ++++++++++++++++++- src/NHibernate.Test/Linq/ConstantTest.cs | 31 ++++++++++++++++++ src/NHibernate/Linq/NhLinqExpression.cs | 11 ++++--- 3 files changed, 69 insertions(+), 5 deletions(-) diff --git a/src/NHibernate.Test/Async/Linq/ConstantTest.cs b/src/NHibernate.Test/Async/Linq/ConstantTest.cs index 64d7037be80..6377ba81862 100644 --- a/src/NHibernate.Test/Async/Linq/ConstantTest.cs +++ b/src/NHibernate.Test/Async/Linq/ConstantTest.cs @@ -13,10 +13,10 @@ using System.Reflection; 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 { @@ -237,6 +237,36 @@ public async Task PlansAreCachedAsync() } } + [Test] + public async Task DmlPlansAreCachedAsync() + { + var queryPlanCacheType = typeof(QueryPlanCache); + + var cache = (SoftLimitMRUCache) + queryPlanCacheType + .GetField("planCache", BindingFlags.Instance | BindingFlags.NonPublic) + .GetValue(Sfi.QueryPlanCache); + cache.Clear(); + + await (db.Customers.Where(c => c.CustomerId == "ALFKI").UpdateAsync(x => new Customer {CompanyName = x.CompanyName})); + Assert.That( + cache, + Has.Count.EqualTo(1), + "First query plan should be cached."); + + using (var spy = new LogSpy(queryPlanCacheType)) + { + // Should hit plan cache. + await (db.Customers.Where(c => c.CustomerId == "ALFKI").UpdateAsync(x => new Customer {CompanyName = x.CompanyName})); + Assert.That(cache, Has.Count.EqualTo(1), "Second query should not cause a plan to be cache."); + Assert.That( + spy.GetWholeLog(), + Does + .Contain("located HQL query plan in cache") + .And.Not.Contain("unable to locate HQL query plan in cache")); + } + } + [Test] public async Task PlansWithNonParameterizedConstantsAreNotCachedAsync() { diff --git a/src/NHibernate.Test/Linq/ConstantTest.cs b/src/NHibernate.Test/Linq/ConstantTest.cs index 2b4be96a912..c5470d9a596 100644 --- a/src/NHibernate.Test/Linq/ConstantTest.cs +++ b/src/NHibernate.Test/Linq/ConstantTest.cs @@ -3,6 +3,7 @@ using System.Reflection; using NHibernate.DomainModel.Northwind.Entities; using NHibernate.Engine.Query; +using NHibernate.Linq; using NHibernate.Linq.Visitors; using NHibernate.Util; using NUnit.Framework; @@ -257,6 +258,36 @@ public void PlansAreCached() } } + [Test] + public void DmlPlansAreCached() + { + var queryPlanCacheType = typeof(QueryPlanCache); + + var cache = (SoftLimitMRUCache) + queryPlanCacheType + .GetField("planCache", BindingFlags.Instance | BindingFlags.NonPublic) + .GetValue(Sfi.QueryPlanCache); + cache.Clear(); + + db.Customers.Where(c => c.CustomerId == "ALFKI").Update(x => new Customer {CompanyName = x.CompanyName}); + Assert.That( + cache, + Has.Count.EqualTo(1), + "First query plan should be cached."); + + using (var spy = new LogSpy(queryPlanCacheType)) + { + // Should hit plan cache. + db.Customers.Where(c => c.CustomerId == "ALFKI").Update(x => new Customer {CompanyName = x.CompanyName}); + Assert.That(cache, Has.Count.EqualTo(1), "Second query should not cause a plan to be cache."); + Assert.That( + spy.GetWholeLog(), + Does + .Contain("located HQL query plan in cache") + .And.Not.Contain("unable to locate HQL query plan in cache")); + } + } + [Test] public void PlansWithNonParameterizedConstantsAreNotCached() { diff --git a/src/NHibernate/Linq/NhLinqExpression.cs b/src/NHibernate/Linq/NhLinqExpression.cs index 50ec325c47f..d508a7ecddb 100644 --- a/src/NHibernate/Linq/NhLinqExpression.cs +++ b/src/NHibernate/Linq/NhLinqExpression.cs @@ -88,13 +88,16 @@ public IASTNode Translate(ISessionFactoryImplementor sessionFactory, bool filter ParameterDescriptors = requiredHqlParameters.AsReadOnly(); - CanCachePlan = CanCachePlan && - // If some constants do not have matching HQL parameters, their values from first query will - // be embedded in the plan and reused for subsequent queries: do not cache the plan. - !ParameterValuesByName + if (QueryMode == QueryMode.Select && CanCachePlan) + { + CanCachePlan = + // If some constants do not have matching HQL parameters, their values from first query will + // be embedded in the plan and reused for subsequent queries: do not cache the plan. + !ParameterValuesByName .Keys .Except(requiredHqlParameters.Select(p => p.Name)) .Any(); + } // The ast node may be altered by caller, duplicate it for preserving the original one. return DuplicateTree(ExpressionToHqlTranslationResults.Statement.AstNode); From 7acee28aa52b4da1a30a712301991cb92dfdac89 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Thu, 26 Sep 2019 09:12:15 +0300 Subject: [PATCH 2/3] Update test --- .../Async/Linq/ConstantTest.cs | 32 +++++++++++-------- src/NHibernate.Test/Linq/ConstantTest.cs | 32 +++++++++++-------- 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/src/NHibernate.Test/Async/Linq/ConstantTest.cs b/src/NHibernate.Test/Async/Linq/ConstantTest.cs index 6377ba81862..95a22c32fb9 100644 --- a/src/NHibernate.Test/Async/Linq/ConstantTest.cs +++ b/src/NHibernate.Test/Async/Linq/ConstantTest.cs @@ -248,22 +248,28 @@ public async Task DmlPlansAreCachedAsync() .GetValue(Sfi.QueryPlanCache); cache.Clear(); - await (db.Customers.Where(c => c.CustomerId == "ALFKI").UpdateAsync(x => new Customer {CompanyName = x.CompanyName})); - Assert.That( - cache, - Has.Count.EqualTo(1), - "First query plan should be cached."); - - using (var spy = new LogSpy(queryPlanCacheType)) + using (session.BeginTransaction()) { - // Should hit plan cache. await (db.Customers.Where(c => c.CustomerId == "ALFKI").UpdateAsync(x => new Customer {CompanyName = x.CompanyName})); - Assert.That(cache, Has.Count.EqualTo(1), "Second query should not cause a plan to be cache."); Assert.That( - spy.GetWholeLog(), - Does - .Contain("located HQL query plan in cache") - .And.Not.Contain("unable to locate HQL query plan in cache")); + cache, + Has.Count.EqualTo(1), + "First query plan should be cached."); + + using (var spy = new LogSpy(queryPlanCacheType)) + { + // Should hit plan cache. + await (db.Customers.Where(c => c.CustomerId == "ANATR").UpdateAsync(x => new Customer {CompanyName = x.CompanyName})); + Assert.That(cache, Has.Count.EqualTo(1), "Second query should not cause a plan to be cached."); + Assert.That( + spy.GetWholeLog(), + Does + .Contain("located HQL query plan in cache") + .And.Not.Contain("unable to locate HQL query plan in cache")); + + await (db.Customers.Where(c => c.CustomerId == "ANATR").UpdateAsync(x => new Customer {ContactName = x.ContactName})); + Assert.That(cache, Has.Count.EqualTo(2), "Third query should be cached"); + } } } diff --git a/src/NHibernate.Test/Linq/ConstantTest.cs b/src/NHibernate.Test/Linq/ConstantTest.cs index c5470d9a596..075b539b894 100644 --- a/src/NHibernate.Test/Linq/ConstantTest.cs +++ b/src/NHibernate.Test/Linq/ConstantTest.cs @@ -269,22 +269,28 @@ public void DmlPlansAreCached() .GetValue(Sfi.QueryPlanCache); cache.Clear(); - db.Customers.Where(c => c.CustomerId == "ALFKI").Update(x => new Customer {CompanyName = x.CompanyName}); - Assert.That( - cache, - Has.Count.EqualTo(1), - "First query plan should be cached."); - - using (var spy = new LogSpy(queryPlanCacheType)) + using (session.BeginTransaction()) { - // Should hit plan cache. db.Customers.Where(c => c.CustomerId == "ALFKI").Update(x => new Customer {CompanyName = x.CompanyName}); - Assert.That(cache, Has.Count.EqualTo(1), "Second query should not cause a plan to be cache."); Assert.That( - spy.GetWholeLog(), - Does - .Contain("located HQL query plan in cache") - .And.Not.Contain("unable to locate HQL query plan in cache")); + cache, + Has.Count.EqualTo(1), + "First query plan should be cached."); + + using (var spy = new LogSpy(queryPlanCacheType)) + { + // Should hit plan cache. + db.Customers.Where(c => c.CustomerId == "ANATR").Update(x => new Customer {CompanyName = x.CompanyName}); + Assert.That(cache, Has.Count.EqualTo(1), "Second query should not cause a plan to be cached."); + Assert.That( + spy.GetWholeLog(), + Does + .Contain("located HQL query plan in cache") + .And.Not.Contain("unable to locate HQL query plan in cache")); + + db.Customers.Where(c => c.CustomerId == "ANATR").Update(x => new Customer {ContactName = x.ContactName}); + Assert.That(cache, Has.Count.EqualTo(2), "Third query should be cached"); + } } } From d31e655e7790a3a2b33389f805e4ecb4e9ff101f Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Fri, 20 Dec 2019 11:09:09 +0200 Subject: [PATCH 3/3] Make sure constants are not cached --- .../Async/Linq/ConstantTest.cs | 28 +++++++++++++++---- src/NHibernate.Test/Linq/ConstantTest.cs | 28 +++++++++++++++---- 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/src/NHibernate.Test/Async/Linq/ConstantTest.cs b/src/NHibernate.Test/Async/Linq/ConstantTest.cs index 95a22c32fb9..04b1f02fbf3 100644 --- a/src/NHibernate.Test/Async/Linq/ConstantTest.cs +++ b/src/NHibernate.Test/Async/Linq/ConstantTest.cs @@ -250,17 +250,33 @@ public async Task DmlPlansAreCachedAsync() using (session.BeginTransaction()) { + await (db.Customers.Where(c => c.CustomerId == "UNKNOWN").UpdateAsync(x => new Customer {CompanyName = "Constant1"})); await (db.Customers.Where(c => c.CustomerId == "ALFKI").UpdateAsync(x => new Customer {CompanyName = x.CompanyName})); Assert.That( cache, - Has.Count.EqualTo(1), - "First query plan should be cached."); + Has.Count.EqualTo(2), + "Query plans should be cached."); using (var spy = new LogSpy(queryPlanCacheType)) { - // Should hit plan cache. - await (db.Customers.Where(c => c.CustomerId == "ANATR").UpdateAsync(x => new Customer {CompanyName = x.CompanyName})); - Assert.That(cache, Has.Count.EqualTo(1), "Second query should not cause a plan to be cached."); + //Queries below should hit plan cache. + using (var sqlSpy = new SqlLogSpy()) + { + await (db.Customers.Where(c => c.CustomerId == "ANATR").UpdateAsync(x => new Customer {CompanyName = x.CompanyName})); + await (db.Customers.Where(c => c.CustomerId == "UNKNOWN").UpdateAsync(x => new Customer {CompanyName = "Constant2"})); + + var sqlEvents = sqlSpy.Appender.GetEvents(); + Assert.That( + sqlEvents[0].RenderedMessage, + Does.Contain("ANATR").And.Not.Contain("UNKNOWN").And.Not.Contain("Constant1"), + "Unexpected constant parameter value"); + Assert.That( + sqlEvents[1].RenderedMessage, + Does.Contain("UNKNOWN").And.Contain("Constant2").And.Not.Contain("Constant1"), + "Unexpected constant parameter value"); + } + + Assert.That(cache, Has.Count.EqualTo(2), "Additional queries should not cause a plan to be cached."); Assert.That( spy.GetWholeLog(), Does @@ -268,7 +284,7 @@ public async Task DmlPlansAreCachedAsync() .And.Not.Contain("unable to locate HQL query plan in cache")); await (db.Customers.Where(c => c.CustomerId == "ANATR").UpdateAsync(x => new Customer {ContactName = x.ContactName})); - Assert.That(cache, Has.Count.EqualTo(2), "Third query should be cached"); + Assert.That(cache, Has.Count.EqualTo(3), "Query should be cached"); } } } diff --git a/src/NHibernate.Test/Linq/ConstantTest.cs b/src/NHibernate.Test/Linq/ConstantTest.cs index 075b539b894..8afb0aa77d3 100644 --- a/src/NHibernate.Test/Linq/ConstantTest.cs +++ b/src/NHibernate.Test/Linq/ConstantTest.cs @@ -271,17 +271,33 @@ public void DmlPlansAreCached() using (session.BeginTransaction()) { + db.Customers.Where(c => c.CustomerId == "UNKNOWN").Update(x => new Customer {CompanyName = "Constant1"}); db.Customers.Where(c => c.CustomerId == "ALFKI").Update(x => new Customer {CompanyName = x.CompanyName}); Assert.That( cache, - Has.Count.EqualTo(1), - "First query plan should be cached."); + Has.Count.EqualTo(2), + "Query plans should be cached."); using (var spy = new LogSpy(queryPlanCacheType)) { - // Should hit plan cache. - db.Customers.Where(c => c.CustomerId == "ANATR").Update(x => new Customer {CompanyName = x.CompanyName}); - Assert.That(cache, Has.Count.EqualTo(1), "Second query should not cause a plan to be cached."); + //Queries below should hit plan cache. + using (var sqlSpy = new SqlLogSpy()) + { + db.Customers.Where(c => c.CustomerId == "ANATR").Update(x => new Customer {CompanyName = x.CompanyName}); + db.Customers.Where(c => c.CustomerId == "UNKNOWN").Update(x => new Customer {CompanyName = "Constant2"}); + + var sqlEvents = sqlSpy.Appender.GetEvents(); + Assert.That( + sqlEvents[0].RenderedMessage, + Does.Contain("ANATR").And.Not.Contain("UNKNOWN").And.Not.Contain("Constant1"), + "Unexpected constant parameter value"); + Assert.That( + sqlEvents[1].RenderedMessage, + Does.Contain("UNKNOWN").And.Contain("Constant2").And.Not.Contain("Constant1"), + "Unexpected constant parameter value"); + } + + Assert.That(cache, Has.Count.EqualTo(2), "Additional queries should not cause a plan to be cached."); Assert.That( spy.GetWholeLog(), Does @@ -289,7 +305,7 @@ public void DmlPlansAreCached() .And.Not.Contain("unable to locate HQL query plan in cache")); db.Customers.Where(c => c.CustomerId == "ANATR").Update(x => new Customer {ContactName = x.ContactName}); - Assert.That(cache, Has.Count.EqualTo(2), "Third query should be cached"); + Assert.That(cache, Has.Count.EqualTo(3), "Query should be cached"); } } }