Skip to content

Commit 9370ffc

Browse files
NH-2319 - Test case dedicated to plan cache previous bug, to be squashed
1 parent fc133bf commit 9370ffc

File tree

3 files changed

+150
-27
lines changed

3 files changed

+150
-27
lines changed

Tools/packages.config

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@
77
<package id="NUnit.Extension.NUnitV2ResultWriter" version="3.6.0" targetFramework="net461" />
88
<package id="NUnit.Extension.TeamCityEventListener" version="1.0.2" targetFramework="net461" />
99
<package id="NUnit.Extension.VSProjectLoader" version="3.6.0" targetFramework="net461" />
10-
<package id="CSharpAsyncGenerator.CommandLine" version="0.4.3" targetFramework="net461" />
10+
<package id="CSharpAsyncGenerator.CommandLine" version="0.4.4" targetFramework="net461" />
1111
</packages>

src/NHibernate.Test/Async/NHSpecificTest/NH2319/Fixture.cs

Lines changed: 76 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,39 +10,50 @@
1010

1111
using System;
1212
using System.Linq;
13+
using System.Reflection;
1314
using NHibernate.Cfg;
1415
using NHibernate.Cfg.MappingSchema;
1516
using NHibernate.Collection;
17+
using NHibernate.Engine.Query;
1618
using NHibernate.Mapping.ByCode;
19+
using NHibernate.Util;
1720
using NUnit.Framework;
1821
using NHibernate.Linq;
1922

2023
namespace NHibernate.Test.NHSpecificTest.NH2319
2124
{
2225
using System.Threading.Tasks;
26+
using System.Threading;
2327
[TestFixture]
2428
public abstract class FixtureBaseAsync : TestCaseMappingByCode
2529
{
26-
private Guid _parentId;
30+
private Guid _parent1Id;
2731
private Guid _child1Id;
32+
private Guid _parent2Id;
33+
private Guid _child3Id;
2834

2935
[Test]
30-
public async Task ShouldBeAbleToFindChildrenByNameAsync()
36+
public Task ShouldBeAbleToFindChildrenByNameAsync()
37+
{
38+
return FindChildrenByNameAsync(_parent1Id, _child1Id);
39+
}
40+
41+
private async Task FindChildrenByNameAsync(Guid parentId, Guid childId, CancellationToken cancellationToken = default(CancellationToken))
3142
{
3243
using (var session = OpenSession())
3344
using (session.BeginTransaction())
3445
{
35-
var parent = await (session.GetAsync<Parent>(_parentId));
46+
var parent = await (session.GetAsync<Parent>(parentId, cancellationToken));
3647

3748
Assert.That(parent, Is.Not.Null);
3849

3950
var filtered = await (parent.Children
4051
.AsQueryable()
4152
.Where(x => x.Name == "Jack")
42-
.ToListAsync());
53+
.ToListAsync(cancellationToken));
4354

4455
Assert.That(filtered, Has.Count.EqualTo(1));
45-
Assert.That(filtered[0].Id, Is.EqualTo(_child1Id));
56+
Assert.That(filtered[0].Id, Is.EqualTo(childId));
4657
}
4758
}
4859

@@ -52,7 +63,7 @@ public async Task ShouldBeAbleToPerformComplexFilteringAsync()
5263
using (var session = OpenSession())
5364
using (session.BeginTransaction())
5465
{
55-
var parent = await (session.GetAsync<Parent>(_parentId));
66+
var parent = await (session.GetAsync<Parent>(_parent1Id));
5667

5768
Assert.NotNull(parent);
5869

@@ -67,13 +78,35 @@ public async Task ShouldBeAbleToPerformComplexFilteringAsync()
6778
}
6879
}
6980

81+
[Test]
82+
public async Task ShouldBeAbleToReuseQueryPlanAsync()
83+
{
84+
await (ShouldBeAbleToFindChildrenByNameAsync());
85+
using (var spy = new LogSpy(typeof(QueryPlanCache)))
86+
{
87+
Assert.That(ShouldBeAbleToFindChildrenByNameAsync, Throws.Nothing);
88+
AssertFilterPlanCacheHit(spy);
89+
}
90+
}
91+
92+
[Test]
93+
public async Task ShouldNotMixResultsAsync()
94+
{
95+
await (FindChildrenByNameAsync(_parent1Id, _child1Id));
96+
using (var spy = new LogSpy(typeof(QueryPlanCache)))
97+
{
98+
await (FindChildrenByNameAsync(_parent2Id, _child3Id));
99+
AssertFilterPlanCacheHit(spy);
100+
}
101+
}
102+
70103
[Test]
71104
public async Task ShouldNotInitializeCollectionWhenPerformingQueryAsync()
72105
{
73106
using (var session = OpenSession())
74107
using (session.BeginTransaction())
75108
{
76-
var parent = await (session.GetAsync<Parent>(_parentId));
109+
var parent = await (session.GetAsync<Parent>(_parent1Id));
77110
Assert.That(parent, Is.Not.Null);
78111

79112
var persistentCollection = (IPersistentCollection) parent.Children;
@@ -94,7 +127,7 @@ public async Task ShouldPerformSqlQueryEvenIfCollectionAlreadyInitializedAsync()
94127
using (var session = OpenSession())
95128
using (session.BeginTransaction())
96129
{
97-
var parent = await (session.GetAsync<Parent>(_parentId));
130+
var parent = await (session.GetAsync<Parent>(_parent1Id));
98131
Assert.That(parent, Is.Not.Null);
99132

100133
var loaded = parent.Children.ToList();
@@ -120,7 +153,7 @@ public async Task TestFilterAsync()
120153
using (var session = OpenSession())
121154
using (session.BeginTransaction())
122155
{
123-
var parent = await (session.GetAsync<Parent>(_parentId));
156+
var parent = await (session.GetAsync<Parent>(_parent1Id));
124157
Assert.That(parent, Is.Not.Null);
125158

126159
var children = await ((await (session.CreateFilterAsync(parent.Children, "where this.Name = 'Jack'")))
@@ -130,6 +163,35 @@ public async Task TestFilterAsync()
130163
}
131164
}
132165

166+
[Test]
167+
public async Task TestPlanCacheMissAsync()
168+
{
169+
var internalPlanCache = typeof(QueryPlanCache)
170+
.GetField("planCache", BindingFlags.NonPublic | BindingFlags.Instance)
171+
?.GetValue(Sfi.QueryPlanCache) as SoftLimitMRUCache;
172+
Assert.That(internalPlanCache, Is.Not.Null,
173+
$"Unable to find the internal query plan cache for clearing it, please adapt code to current {nameof(QueryPlanCache)} implementation.");
174+
175+
using (var spy = new LogSpy(typeof(QueryPlanCache)))
176+
{
177+
internalPlanCache.Clear();
178+
await (ShouldBeAbleToFindChildrenByNameAsync());
179+
AssertFilterPlanCacheMiss(spy);
180+
}
181+
}
182+
183+
private const string _filterPlanCacheMissLog = "unable to locate collection-filter query plan in cache";
184+
185+
private static void AssertFilterPlanCacheHit(LogSpy spy) =>
186+
// Each query currently ask the cache two times, so asserting reuse requires to check cache has not been missed
187+
// rather than only asserting it has been hit.
188+
Assert.That(spy.GetWholeLog(),
189+
Contains.Substring("located collection-filter query plan in cache (")
190+
.And.Not.Contains(_filterPlanCacheMissLog));
191+
192+
private static void AssertFilterPlanCacheMiss(LogSpy spy) =>
193+
Assert.That(spy.GetWholeLog(), Contains.Substring(_filterPlanCacheMissLog));
194+
133195
protected override void Configure(Configuration configuration)
134196
{
135197
configuration.SetProperty("show_sql", "true");
@@ -141,11 +203,11 @@ protected override void OnSetUp()
141203
using (var session = OpenSession())
142204
using (var transaction = session.BeginTransaction())
143205
{
144-
var parent1 = new Parent {Name = "Bob"};
145-
_parentId = (Guid) session.Save(parent1);
206+
var parent1 = new Parent { Name = "Bob" };
207+
_parent1Id = (Guid) session.Save(parent1);
146208

147-
var parent2 = new Parent {Name = "Martin"};
148-
session.Save(parent2);
209+
var parent2 = new Parent { Name = "Martin" };
210+
_parent2Id = (Guid) session.Save(parent2);
149211

150212
var child1 = new Child
151213
{
@@ -185,7 +247,7 @@ protected override void OnSetUp()
185247
Parent = parent2
186248
};
187249
parent2.Children.Add(child3);
188-
session.Save(child3);
250+
_child3Id = (Guid) session.Save(child3);
189251

190252
session.Flush();
191253
transaction.Commit();

src/NHibernate.Test/NHSpecificTest/NH2319/Fixture.cs

Lines changed: 73 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,36 @@
11
using System;
22
using System.Linq;
3+
using System.Reflection;
34
using NHibernate.Cfg;
45
using NHibernate.Cfg.MappingSchema;
56
using NHibernate.Collection;
7+
using NHibernate.Engine.Query;
68
using NHibernate.Mapping.ByCode;
9+
using NHibernate.Util;
710
using NUnit.Framework;
811

912
namespace NHibernate.Test.NHSpecificTest.NH2319
1013
{
1114
[TestFixture]
1215
public abstract class FixtureBase : TestCaseMappingByCode
1316
{
14-
private Guid _parentId;
17+
private Guid _parent1Id;
1518
private Guid _child1Id;
19+
private Guid _parent2Id;
20+
private Guid _child3Id;
1621

1722
[Test]
1823
public void ShouldBeAbleToFindChildrenByName()
24+
{
25+
FindChildrenByName(_parent1Id, _child1Id);
26+
}
27+
28+
private void FindChildrenByName(Guid parentId, Guid childId)
1929
{
2030
using (var session = OpenSession())
2131
using (session.BeginTransaction())
2232
{
23-
var parent = session.Get<Parent>(_parentId);
33+
var parent = session.Get<Parent>(parentId);
2434

2535
Assert.That(parent, Is.Not.Null);
2636

@@ -30,7 +40,7 @@ public void ShouldBeAbleToFindChildrenByName()
3040
.ToList();
3141

3242
Assert.That(filtered, Has.Count.EqualTo(1));
33-
Assert.That(filtered[0].Id, Is.EqualTo(_child1Id));
43+
Assert.That(filtered[0].Id, Is.EqualTo(childId));
3444
}
3545
}
3646

@@ -40,7 +50,7 @@ public void ShouldBeAbleToPerformComplexFiltering()
4050
using (var session = OpenSession())
4151
using (session.BeginTransaction())
4252
{
43-
var parent = session.Get<Parent>(_parentId);
53+
var parent = session.Get<Parent>(_parent1Id);
4454

4555
Assert.NotNull(parent);
4656

@@ -55,13 +65,35 @@ public void ShouldBeAbleToPerformComplexFiltering()
5565
}
5666
}
5767

68+
[Test]
69+
public void ShouldBeAbleToReuseQueryPlan()
70+
{
71+
ShouldBeAbleToFindChildrenByName();
72+
using (var spy = new LogSpy(typeof(QueryPlanCache)))
73+
{
74+
Assert.That(ShouldBeAbleToFindChildrenByName, Throws.Nothing);
75+
AssertFilterPlanCacheHit(spy);
76+
}
77+
}
78+
79+
[Test]
80+
public void ShouldNotMixResults()
81+
{
82+
FindChildrenByName(_parent1Id, _child1Id);
83+
using (var spy = new LogSpy(typeof(QueryPlanCache)))
84+
{
85+
FindChildrenByName(_parent2Id, _child3Id);
86+
AssertFilterPlanCacheHit(spy);
87+
}
88+
}
89+
5890
[Test]
5991
public void ShouldNotInitializeCollectionWhenPerformingQuery()
6092
{
6193
using (var session = OpenSession())
6294
using (session.BeginTransaction())
6395
{
64-
var parent = session.Get<Parent>(_parentId);
96+
var parent = session.Get<Parent>(_parent1Id);
6597
Assert.That(parent, Is.Not.Null);
6698

6799
var persistentCollection = (IPersistentCollection) parent.Children;
@@ -82,7 +114,7 @@ public void ShouldPerformSqlQueryEvenIfCollectionAlreadyInitialized()
82114
using (var session = OpenSession())
83115
using (session.BeginTransaction())
84116
{
85-
var parent = session.Get<Parent>(_parentId);
117+
var parent = session.Get<Parent>(_parent1Id);
86118
Assert.That(parent, Is.Not.Null);
87119

88120
var loaded = parent.Children.ToList();
@@ -108,7 +140,7 @@ public void TestFilter()
108140
using (var session = OpenSession())
109141
using (session.BeginTransaction())
110142
{
111-
var parent = session.Get<Parent>(_parentId);
143+
var parent = session.Get<Parent>(_parent1Id);
112144
Assert.That(parent, Is.Not.Null);
113145

114146
var children = session.CreateFilter(parent.Children, "where this.Name = 'Jack'")
@@ -118,6 +150,35 @@ public void TestFilter()
118150
}
119151
}
120152

153+
[Test]
154+
public void TestPlanCacheMiss()
155+
{
156+
var internalPlanCache = typeof(QueryPlanCache)
157+
.GetField("planCache", BindingFlags.NonPublic | BindingFlags.Instance)
158+
?.GetValue(Sfi.QueryPlanCache) as SoftLimitMRUCache;
159+
Assert.That(internalPlanCache, Is.Not.Null,
160+
$"Unable to find the internal query plan cache for clearing it, please adapt code to current {nameof(QueryPlanCache)} implementation.");
161+
162+
using (var spy = new LogSpy(typeof(QueryPlanCache)))
163+
{
164+
internalPlanCache.Clear();
165+
ShouldBeAbleToFindChildrenByName();
166+
AssertFilterPlanCacheMiss(spy);
167+
}
168+
}
169+
170+
private const string _filterPlanCacheMissLog = "unable to locate collection-filter query plan in cache";
171+
172+
private static void AssertFilterPlanCacheHit(LogSpy spy) =>
173+
// Each query currently ask the cache two times, so asserting reuse requires to check cache has not been missed
174+
// rather than only asserting it has been hit.
175+
Assert.That(spy.GetWholeLog(),
176+
Contains.Substring("located collection-filter query plan in cache (")
177+
.And.Not.Contains(_filterPlanCacheMissLog));
178+
179+
private static void AssertFilterPlanCacheMiss(LogSpy spy) =>
180+
Assert.That(spy.GetWholeLog(), Contains.Substring(_filterPlanCacheMissLog));
181+
121182
protected override void Configure(Configuration configuration)
122183
{
123184
configuration.SetProperty("show_sql", "true");
@@ -129,11 +190,11 @@ protected override void OnSetUp()
129190
using (var session = OpenSession())
130191
using (var transaction = session.BeginTransaction())
131192
{
132-
var parent1 = new Parent {Name = "Bob"};
133-
_parentId = (Guid) session.Save(parent1);
193+
var parent1 = new Parent { Name = "Bob" };
194+
_parent1Id = (Guid) session.Save(parent1);
134195

135-
var parent2 = new Parent {Name = "Martin"};
136-
session.Save(parent2);
196+
var parent2 = new Parent { Name = "Martin" };
197+
_parent2Id = (Guid) session.Save(parent2);
137198

138199
var child1 = new Child
139200
{
@@ -173,7 +234,7 @@ protected override void OnSetUp()
173234
Parent = parent2
174235
};
175236
parent2.Children.Add(child3);
176-
session.Save(child3);
237+
_child3Id = (Guid) session.Save(child3);
177238

178239
session.Flush();
179240
transaction.Commit();

0 commit comments

Comments
 (0)