Skip to content

Commit 7c1a2cd

Browse files
authored
Allow specifying the size of the query plan cache (#2645)
1 parent 33f90d3 commit 7c1a2cd

File tree

9 files changed

+97
-73
lines changed

9 files changed

+97
-73
lines changed

src/NHibernate.Test/Async/NHSpecificTest/NH3050/FixtureByCode.cs

+7-33
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using NUnit.Framework;
1616
using System.Collections.Generic;
1717
using System;
18+
using NHibernate.Cfg;
1819

1920
namespace NHibernate.Test.NHSpecificTest.NH3050
2021
{
@@ -70,12 +71,15 @@ protected override void OnTearDown()
7071
}
7172
}
7273

73-
[Test]
74-
public async Task NH3050_ReproductionAsync()
74+
protected override void Configure(Configuration configuration)
7575
{
7676
//firstly to make things simpler, we set the query plan cache size to 1
77-
Assert.IsTrue(TrySetQueryPlanCacheSize(Sfi, 1));
77+
configuration.Properties[Cfg.Environment.QueryPlanCacheMaxSize] = "1";
78+
}
7879

80+
[Test]
81+
public async Task NH3050_ReproductionAsync()
82+
{
7983
using (ISession session = OpenSession())
8084
using (session.BeginTransaction())
8185
{
@@ -105,35 +109,5 @@ where names.Contains(e.Name)
105109
await (query.ToListAsync());
106110
}
107111
}
108-
109-
/// <summary>
110-
/// Uses reflection to create a new SoftLimitMRUCache with a specified size and sets session factory query plan cache to it.
111-
/// This is done like this as NHibernate does not currently provide any way to specify the query plan cache size through configuration.
112-
/// </summary>
113-
/// <param name="factory"></param>
114-
/// <param name="size"></param>
115-
/// <returns></returns>
116-
private static bool TrySetQueryPlanCacheSize(ISessionFactory factory, int size)
117-
{
118-
var factoryImpl = (factory as DebugSessionFactory)?.ActualFactory as Impl.SessionFactoryImpl;
119-
if (factoryImpl != null)
120-
{
121-
var queryPlanCacheFieldInfo = typeof(Impl.SessionFactoryImpl).GetField("queryPlanCache", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
122-
if (queryPlanCacheFieldInfo != null)
123-
{
124-
var queryPlanCache = (Engine.Query.QueryPlanCache)queryPlanCacheFieldInfo.GetValue(factoryImpl);
125-
126-
var planCacheFieldInfo = typeof(Engine.Query.QueryPlanCache).GetField("planCache", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
127-
if (planCacheFieldInfo != null)
128-
{
129-
var softLimitMRUCache = new Util.SoftLimitMRUCache(size);
130-
131-
planCacheFieldInfo.SetValue(queryPlanCache, softLimitMRUCache);
132-
return true;
133-
}
134-
}
135-
}
136-
return false;
137-
}
138112
}
139113
}

src/NHibernate.Test/CfgTest/SettingsFactoryFixture.cs

+37-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using System.Collections.Generic;
23
using NHibernate.Cfg;
34
using NUnit.Framework;
@@ -11,12 +12,44 @@ public class SettingsFactoryFixture
1112
public void DefaultValueForKeyWords()
1213
{
1314
var properties = new Dictionary<string, string>
14-
{
15-
{"dialect", typeof (Dialect.MsSql2005Dialect).FullName}
16-
};
15+
{
16+
{"dialect", typeof (Dialect.MsSql2005Dialect).FullName}
17+
};
1718
var settings = new SettingsFactory().BuildSettings(properties);
1819
Assert.That(settings.IsKeywordsImportEnabled);
1920
Assert.That(!settings.IsAutoQuoteEnabled);
2021
}
22+
23+
[Test,TestCaseSource(nameof(TestCases))]
24+
public object ReadsSettingsCorrectly(string key, string value, Func<Settings,object> settingsProp)
25+
{
26+
//Dialect needed to prevent exception
27+
var properties = new Dictionary<string, string>
28+
{
29+
{"dialect", typeof (Dialect.MsSql2005Dialect).FullName}
30+
};
31+
32+
properties[key] = value;
33+
34+
var settings = new SettingsFactory().BuildSettings(properties);
35+
36+
return settingsProp(settings);
37+
}
38+
39+
public static IEnumerable<TestCaseData> TestCases
40+
{
41+
get
42+
{
43+
yield return new SettingsTestCaseData("query.plan_cache_max_size", "256", x => x.QueryPlanCacheMaxSize).Returns(256);
44+
yield return new SettingsTestCaseData("query.plan_parameter_metadata_max_size", "512", x => x.QueryPlanCacheParameterMetadataMaxSize).Returns(512);
45+
}
46+
}
47+
48+
private class SettingsTestCaseData : TestCaseData
49+
{
50+
public SettingsTestCaseData(string key, string value, Func<Settings, object> settingsProp) : base(key, value, settingsProp)
51+
{
52+
}
53+
}
2154
}
22-
}
55+
}

src/NHibernate.Test/NHSpecificTest/NH3050/FixtureByCode.cs

+7-33
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using NUnit.Framework;
66
using System.Collections.Generic;
77
using System;
8+
using NHibernate.Cfg;
89

910
namespace NHibernate.Test.NHSpecificTest.NH3050
1011
{
@@ -59,12 +60,15 @@ protected override void OnTearDown()
5960
}
6061
}
6162

62-
[Test]
63-
public void NH3050_Reproduction()
63+
protected override void Configure(Configuration configuration)
6464
{
6565
//firstly to make things simpler, we set the query plan cache size to 1
66-
Assert.IsTrue(TrySetQueryPlanCacheSize(Sfi, 1));
66+
configuration.Properties[Cfg.Environment.QueryPlanCacheMaxSize] = "1";
67+
}
6768

69+
[Test]
70+
public void NH3050_Reproduction()
71+
{
6872
using (ISession session = OpenSession())
6973
using (session.BeginTransaction())
7074
{
@@ -94,35 +98,5 @@ where names.Contains(e.Name)
9498
query.ToList();
9599
}
96100
}
97-
98-
/// <summary>
99-
/// Uses reflection to create a new SoftLimitMRUCache with a specified size and sets session factory query plan cache 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(ISessionFactory factory, int size)
106-
{
107-
var factoryImpl = (factory as DebugSessionFactory)?.ActualFactory as Impl.SessionFactoryImpl;
108-
if (factoryImpl != null)
109-
{
110-
var queryPlanCacheFieldInfo = typeof(Impl.SessionFactoryImpl).GetField("queryPlanCache", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
111-
if (queryPlanCacheFieldInfo != null)
112-
{
113-
var queryPlanCache = (Engine.Query.QueryPlanCache)queryPlanCacheFieldInfo.GetValue(factoryImpl);
114-
115-
var planCacheFieldInfo = typeof(Engine.Query.QueryPlanCache).GetField("planCache", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
116-
if (planCacheFieldInfo != null)
117-
{
118-
var softLimitMRUCache = new Util.SoftLimitMRUCache(size);
119-
120-
planCacheFieldInfo.SetValue(queryPlanCache, softLimitMRUCache);
121-
return true;
122-
}
123-
}
124-
}
125-
return false;
126-
}
127101
}
128102
}

src/NHibernate/Cfg/Environment.cs

+25
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using NHibernate.Bytecode;
66
using NHibernate.Cfg.ConfigurationSchema;
77
using NHibernate.Engine;
8+
using NHibernate.Engine.Query;
89
using NHibernate.Linq;
910
using NHibernate.Linq.Visitors;
1011
using NHibernate.MultiTenancy;
@@ -396,6 +397,30 @@ public static string Version
396397
/// </summary>
397398
public const string MultiTenancyConnectionProvider = "multi_tenancy.connection_provider";
398399

400+
/// <summary>
401+
/// The maximum number of entries including:
402+
/// <list>
403+
/// <item>
404+
/// <see cref="HQLQueryPlan"/>
405+
/// </item>
406+
/// <item>
407+
/// <see cref="NativeSQLQueryPlan"/>
408+
/// </item>
409+
/// <item>
410+
/// <see cref="FilterQueryPlan"/>
411+
/// </item>
412+
/// </list>
413+
///
414+
/// maintained by <see cref="QueryPlanCache"/>. Default is 128.
415+
/// </summary>
416+
public const string QueryPlanCacheMaxSize = "query.plan_cache_max_size";
417+
418+
/// <summary>
419+
/// The maximum number of <see cref="ParameterMetadata"/> maintained
420+
/// by <see cref="QueryPlanCache"/>. Default is 128.
421+
/// </summary>
422+
public const string QueryPlanCacheParameterMetadataMaxSize = "query.plan_parameter_metadata_max_size";
423+
399424
private static IBytecodeProvider BytecodeProviderInstance;
400425
private static bool EnableReflectionOptimizer;
401426

src/NHibernate/Cfg/Settings.cs

+2
Original file line numberDiff line numberDiff line change
@@ -212,5 +212,7 @@ internal string GetFullCacheRegionName(string name)
212212
public MultiTenancyStrategy MultiTenancyStrategy { get; internal set; }
213213

214214
public IMultiTenancyConnectionProvider MultiTenancyConnectionProvider { get; internal set; }
215+
public int QueryPlanCacheParameterMetadataMaxSize { get; internal set; }
216+
public int QueryPlanCacheMaxSize { get; internal set; }
215217
}
216218
}

src/NHibernate/Cfg/SettingsFactory.cs

+5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using NHibernate.Cache;
88
using NHibernate.Connection;
99
using NHibernate.Dialect;
10+
using NHibernate.Engine.Query;
1011
using NHibernate.Exceptions;
1112
using NHibernate.Hql;
1213
using NHibernate.Linq;
@@ -319,6 +320,10 @@ public Settings BuildSettings(IDictionary<string, string> properties)
319320
settings.LinqPreTransformer = NhRelinqQueryParser.CreatePreTransformer(settings.PreTransformerRegistrar);
320321
}
321322

323+
//QueryPlanCache:
324+
settings.QueryPlanCacheParameterMetadataMaxSize = PropertiesHelper.GetInt32(Environment.QueryPlanCacheParameterMetadataMaxSize, properties, QueryPlanCache.DefaultParameterMetadataMaxCount);
325+
settings.QueryPlanCacheMaxSize = PropertiesHelper.GetInt32(Environment.QueryPlanCacheMaxSize, properties, QueryPlanCache.DefaultQueryPlanMaxCount);
326+
322327
// NHibernate-specific:
323328
settings.IsolationLevel = isolation;
324329

src/NHibernate/Engine/Query/QueryPlanCache.cs

+8-2
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,20 @@ public class QueryPlanCache
2323
// unnecessary cache entries.
2424
// Used solely for caching param metadata for native-sql queries, see
2525
// getSQLParameterMetadata() for a discussion as to why...
26-
private readonly SimpleMRUCache sqlParamMetadataCache = new SimpleMRUCache();
26+
private readonly SimpleMRUCache sqlParamMetadataCache;
2727

2828
// the cache of the actual plans...
29-
private readonly SoftLimitMRUCache planCache = new SoftLimitMRUCache(128);
29+
private readonly SoftLimitMRUCache planCache;
30+
31+
internal const int DefaultParameterMetadataMaxCount = 128;
32+
internal const int DefaultQueryPlanMaxCount = 128;
3033

3134
public QueryPlanCache(ISessionFactoryImplementor factory)
3235
{
3336
this.factory = factory;
37+
38+
sqlParamMetadataCache = new SimpleMRUCache(factory.Settings.QueryPlanCacheParameterMetadataMaxSize);
39+
planCache = new SoftLimitMRUCache(factory.Settings.QueryPlanCacheMaxSize);
3440
}
3541

3642
public ParameterMetadata GetSQLParameterMetadata(string query)

src/NHibernate/Impl/SessionFactoryImpl.cs

+4-1
Original file line numberDiff line numberDiff line change
@@ -181,12 +181,15 @@ public void HandleEntityNotFound(string entityName, string propertyName, object
181181

182182
public SessionFactoryImpl(Configuration cfg, IMapping mapping, Settings settings, EventListeners listeners)
183183
{
184+
this.settings = settings;
185+
184186
Init();
187+
185188
log.Info("building session factory");
186189

187190
properties = new Dictionary<string, string>(cfg.Properties);
188191
interceptor = cfg.Interceptor;
189-
this.settings = settings;
192+
190193
sqlFunctionRegistry = new SQLFunctionRegistry(settings.Dialect, cfg.SqlFunctions);
191194
eventListeners = listeners;
192195
filters = new Dictionary<string, FilterDefinition>(cfg.FilterDefinitions);

src/NHibernate/nhibernate-configuration.xsd

+2
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@
122122
<xs:enumeration value="query.factory_class" />
123123
<xs:enumeration value="query.linq_provider_class" />
124124
<xs:enumeration value="query.imports" />
125+
<xs:enumeration value="query.plan_parameter_metadata_max_size" />
126+
<xs:enumeration value="query.plan_cache_max_size" />
125127
<xs:enumeration value="hbm2ddl.auto" />
126128
<xs:enumeration value="hbm2ddl.keywords" />
127129
<xs:enumeration value="hbm2ddl.throw_on_update">

0 commit comments

Comments
 (0)