Skip to content

Commit c59e60e

Browse files
Support caching queries with autodiscovered types
fix #3169
1 parent d8a061b commit c59e60e

File tree

5 files changed

+177
-56
lines changed

5 files changed

+177
-56
lines changed

src/NHibernate.Test/SqlTest/Query/NativeSQLQueriesFixture.cs

Lines changed: 66 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
using System.Collections;
22
using System.Linq;
3+
using NHibernate.Criterion;
4+
using NHibernate.Multi;
35
using NHibernate.Transform;
46
using NUnit.Framework;
5-
using NHibernate.Criterion;
67

78
namespace NHibernate.Test.SqlTest.Query
89
{
@@ -51,6 +52,20 @@ protected override string MappingsAssembly
5152
get { return "NHibernate.Test"; }
5253
}
5354

55+
protected override void OnTearDown()
56+
{
57+
using (var session = OpenSession())
58+
using (var transaction = session.BeginTransaction())
59+
{
60+
session.CreateQuery("delete from Employment").ExecuteUpdate();
61+
session.CreateQuery("delete from System.Object").ExecuteUpdate();
62+
63+
transaction.Commit();
64+
}
65+
66+
Sfi.QueryCache.Clear();
67+
}
68+
5469
[Test]
5570
public void FailOnNoAddEntityOrScalar()
5671
{
@@ -125,18 +140,6 @@ public void SQLQueryInterface()
125140
t.Commit();
126141
s.Close();
127142
}
128-
129-
using (var s = OpenSession())
130-
using (var t = s.BeginTransaction())
131-
{
132-
s.Delete(emp);
133-
s.Delete(gavin);
134-
s.Delete(ifa);
135-
s.Delete(jboss);
136-
137-
t.Commit();
138-
s.Close();
139-
}
140143
}
141144

142145
[Test]
@@ -191,18 +194,6 @@ public void SQLQueryInterfaceCacheable()
191194
t.Commit();
192195
s.Close();
193196
}
194-
195-
using (var s = OpenSession())
196-
using (var t = s.BeginTransaction())
197-
{
198-
s.Delete(emp);
199-
s.Delete(gavin);
200-
s.Delete(ifa);
201-
s.Delete(jboss);
202-
203-
t.Commit();
204-
s.Close();
205-
}
206197
}
207198

208199
[Test(Description = "GH-2904")]
@@ -252,20 +243,11 @@ IList GetCacheableSqlQueryResults()
252243
Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "results are expected from cache");
253244
}
254245
}
255-
256-
using (var s = OpenSession())
257-
using (var t = s.BeginTransaction())
258-
{
259-
s.Delete(emp);
260-
s.Delete(gavin);
261-
s.Delete(ifa);
262-
s.Delete(jboss);
263-
t.Commit();
264-
}
265246
}
266247

267248
class ResultDto
268249
{
250+
public long orgId { get; set; }
269251
public string regionCode { get; set; }
270252
}
271253

@@ -294,23 +276,64 @@ void AssertQuery(bool fromCache)
294276
.List();
295277
t.Commit();
296278

297-
Assert.AreEqual(1, l.Count);
298-
//TODO: Uncomment if we properly fix caching auto discovery type queries with transformers
299-
// var msg = "results are expected from " + (fromCache ? "cache" : "DB");
300-
// Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(fromCache ? 0 : 1), msg);
301-
// Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(fromCache ? 1 : 0), msg);
279+
Assert.That(l.Count, Is.EqualTo(1));
280+
var msg = "Results are expected from " + (fromCache ? "cache" : "DB");
281+
Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(fromCache ? 0 : 1), msg);
282+
Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(fromCache ? 1 : 0), msg);
302283
}
303284
}
304285

305286
AssertQuery(false);
306287
AssertQuery(true);
288+
}
307289

308-
using (var s = OpenSession())
309-
using (var t = s.BeginTransaction())
290+
[Test(Description = "GH-3169")]
291+
public void CacheableScalarSQLMultiQueryWithTransformer()
292+
{
293+
Organization ifa = new Organization("IFA");
294+
295+
using (ISession s = OpenSession())
296+
using (ITransaction t = s.BeginTransaction())
310297
{
311-
s.Delete(ifa);
298+
s.Save(ifa);
312299
t.Commit();
313300
}
301+
302+
void AssertQuery(bool fromCache)
303+
{
304+
using (var s = OpenSession())
305+
using (var t = s.BeginTransaction())
306+
using (EnableStatisticsScope())
307+
{
308+
var q1 = s.CreateSQLQuery("select org.NAME as regionCode from ORGANIZATION org")
309+
.AddScalar("regionCode", NHibernateUtil.String)
310+
.SetResultTransformer(Transformers.AliasToBean<ResultDto>())
311+
.SetCacheable(true);
312+
var q2 = s.CreateSQLQuery("select org.ORGID as orgId from ORGANIZATION org")
313+
.AddScalar("orgId", NHibernateUtil.Int64)
314+
.SetResultTransformer(Transformers.AliasToBean<ResultDto>())
315+
.SetCacheable(true);
316+
317+
var batch = s.CreateQueryBatch();
318+
batch.Add<ResultDto>(q1);
319+
batch.Add<ResultDto>(q2);
320+
batch.Execute();
321+
322+
var l1 = batch.GetResult<ResultDto>(0);
323+
var l2 = batch.GetResult<ResultDto>(1);
324+
325+
t.Commit();
326+
327+
Assert.That(l1.Count, Is.EqualTo(1), "Unexpected results count for the first query.");
328+
Assert.That(l2.Count, Is.EqualTo(1), "Unexpected results count for the second query.");
329+
var msg = "Results are expected from " + (fromCache ? "cache" : "DB");
330+
Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(fromCache ? 0 : 2), msg);
331+
Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(fromCache ? 2 : 0), msg);
332+
}
333+
}
334+
335+
AssertQuery(false);
336+
AssertQuery(true);
314337
}
315338

316339
[Test]
@@ -338,11 +361,6 @@ public void ResultSetMappingDefinition()
338361
.List();
339362
Assert.AreEqual(l.Count, 1);
340363

341-
s.Delete(emp);
342-
s.Delete(gavin);
343-
s.Delete(ifa);
344-
s.Delete(jboss);
345-
346364
t.Commit();
347365
s.Close();
348366
}
@@ -425,8 +443,6 @@ public void ScalarValues()
425443
Assert.AreEqual(o[1], "JBoss");
426444
Assert.AreEqual(o[0], idJBoss);
427445

428-
s.Delete(ifa);
429-
s.Delete(jboss);
430446
t.Commit();
431447
s.Close();
432448
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using System;
2+
3+
namespace NHibernate.Cache
4+
{
5+
[Serializable]
6+
public class QueryAliasesKey : IEquatable<QueryAliasesKey>
7+
{
8+
private readonly QueryKey _queryKey;
9+
10+
/// <summary>
11+
/// Initializes a new instance of the <see cref="QueryAliasesKey"/> class.
12+
/// </summary>
13+
/// <param name="queryKey">The <see cref="QueryKey"/> of the query for which aliases have to be stored.</param>
14+
public QueryAliasesKey(QueryKey queryKey)
15+
{
16+
_queryKey = queryKey ?? throw new ArgumentNullException(nameof(queryKey));
17+
}
18+
19+
public override bool Equals(object other)
20+
{
21+
return Equals(other as QueryAliasesKey);
22+
}
23+
24+
public bool Equals(QueryAliasesKey other)
25+
{
26+
return other != null && _queryKey.Equals(other._queryKey);
27+
}
28+
29+
public override int GetHashCode()
30+
{
31+
return _queryKey.GetHashCode();
32+
}
33+
34+
public override string ToString()
35+
{
36+
return "QueryAlisesKey: " + _queryKey.ToString();
37+
}
38+
}
39+
}

src/NHibernate/Cache/StandardQueryCache.cs

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using System.Collections.Generic;
44
using System.Linq;
55
using NHibernate.Cfg;
6-
using NHibernate.Collection;
76
using NHibernate.Engine;
87
using NHibernate.Persister.Collection;
98
using NHibernate.Type;
@@ -86,8 +85,15 @@ public bool Put(
8685
{
8786
// 6.0 TODO: inline the call.
8887
#pragma warning disable 612
89-
return Put(key, returnTypes, result, queryParameters.NaturalKeyLookup, session);
88+
var cached = Put(key, returnTypes, result, queryParameters.NaturalKeyLookup, session);
9089
#pragma warning restore 612
90+
91+
if (cached && key.ResultTransformer?.AutoDiscoverTypes == true && key.ResultTransformer.AutoDiscoveredAliases != null)
92+
{
93+
Cache.Put(new QueryAliasesKey(key), key.ResultTransformer.AutoDiscoveredAliases);
94+
}
95+
96+
return cached;
9197
}
9298

9399
// Since 5.2
@@ -126,8 +132,22 @@ public IList Get(
126132
{
127133
// 6.0 TODO: inline the call.
128134
#pragma warning disable 612
129-
return Get(key, returnTypes, queryParameters.NaturalKeyLookup, spaces, session);
135+
var result = Get(key, returnTypes, queryParameters.NaturalKeyLookup, spaces, session);
130136
#pragma warning restore 612
137+
138+
if (result != null && key.ResultTransformer?.AutoDiscoverTypes == true && result.Count > 0)
139+
{
140+
var aliasesKey = new QueryAliasesKey(key);
141+
if (!(Cache.Get(aliasesKey) is string[] aliases))
142+
{
143+
// Cannot properly initialize the result transformer, treat it as a cache miss
144+
Log.Debug("query aliases were not found in cache: {0}", aliasesKey);
145+
return null;
146+
}
147+
key.ResultTransformer.SupplyAutoDiscoveredParameters(queryParameters.ResultTransformer, aliases);
148+
}
149+
150+
return result;
131151
}
132152
finally
133153
{
@@ -184,9 +204,16 @@ public bool[] PutMany(
184204
if (queryParameters[i].NaturalKeyLookup && result.Count == 0)
185205
continue;
186206

207+
var key = keys[i];
187208
cached[i] = true;
188-
cachedKeys.Add(keys[i]);
209+
cachedKeys.Add(key);
189210
cachedResults.Add(GetCacheableResult(returnTypes[i], session, result, ts));
211+
212+
if (key.ResultTransformer?.AutoDiscoverTypes == true && key.ResultTransformer.AutoDiscoveredAliases != null)
213+
{
214+
cachedKeys.Add(new QueryAliasesKey(key));
215+
cachedResults.Add(key.ResultTransformer.AutoDiscoveredAliases);
216+
}
190217
}
191218

192219
_cache.PutMany(cachedKeys.ToArray(), cachedResults.ToArray());
@@ -244,6 +271,8 @@ public IList[] GetMany(
244271
{
245272
session.PersistenceContext.BatchFetchQueue.InitializeQueryCacheQueue();
246273

274+
var queryAliasesKeys = new QueryAliasesKey[keys.Length];
275+
var hasAliasesToFetch = false;
247276
for (var i = 0; i < keys.Length; i++)
248277
{
249278
var cacheable = (IList) cacheables[i];
@@ -267,6 +296,37 @@ public IList[] GetMany(
267296

268297
finalReturnTypes[i] = GetReturnTypes(key, returnTypes[i], cacheable);
269298
PerformBeforeAssemble(finalReturnTypes[i], session, cacheable);
299+
300+
if (key.ResultTransformer?.AutoDiscoverTypes == true && cacheable.Count > 0)
301+
{
302+
queryAliasesKeys[i] = new QueryAliasesKey(key);
303+
hasAliasesToFetch = true;
304+
}
305+
}
306+
307+
if (hasAliasesToFetch)
308+
{
309+
var allAliases = _cache.GetMany(queryAliasesKeys.Where(k => k != null).ToArray());
310+
311+
var aliasesIndex = 0;
312+
for (var i = 0; i < keys.Length; i++)
313+
{
314+
var queryAliasesKey = queryAliasesKeys[i];
315+
if (queryAliasesKey == null)
316+
continue;
317+
318+
if (allAliases[aliasesIndex] is string[] aliases)
319+
{
320+
keys[i].ResultTransformer.SupplyAutoDiscoveredParameters(queryParameters[i].ResultTransformer, aliases);
321+
}
322+
else
323+
{
324+
// Cannot properly initialize the result transformer, treat it as a cache miss
325+
Log.Debug("query aliases were not found in cache: {0}", queryAliasesKey);
326+
finalReturnTypes[i] = null;
327+
}
328+
aliasesIndex++;
329+
}
270330
}
271331

272332
for (var i = 0; i < keys.Length; i++)

src/NHibernate/Loader/Loader.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1842,8 +1842,7 @@ internal virtual bool IsCacheable(QueryParameters queryParameters)
18421842

18431843
internal bool IsCacheable(QueryParameters queryParameters, bool supportsQueryCache, IEnumerable<IPersister> persisters)
18441844
{
1845-
bool isCacheable = Factory.Settings.IsQueryCacheEnabled && queryParameters.Cacheable
1846-
&& !(queryParameters.HasAutoDiscoverScalarTypes && queryParameters.ResultTransformer != null);
1845+
bool isCacheable = Factory.Settings.IsQueryCacheEnabled && queryParameters.Cacheable;
18471846
if (isCacheable && !supportsQueryCache)
18481847
{
18491848
if (Factory.Settings.QueryThrowNeverCached)

src/NHibernate/Transform/CacheableResultTransformer.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ public class CacheableResultTransformer : IResultTransformer
2323

2424
public bool AutoDiscoverTypes { get; }
2525

26+
/// <summary>
27+
/// The auto-discovered aliaises.
28+
/// </summary>
29+
public string[] AutoDiscoveredAliases { get; private set; }
30+
2631
private readonly SqlString _autoDiscoveredQuery;
2732
private readonly bool _skipTransformer;
2833
private int _tupleLength;
@@ -202,6 +207,8 @@ internal void SupplyAutoDiscoveredParameters(IResultTransformer transformer, str
202207
throw new InvalidOperationException(
203208
"Cannot supply auto-discovered parameters when it is not enabled on the transformer.");
204209

210+
AutoDiscoveredAliases = aliases;
211+
205212
var includeInTuple = ArrayHelper.Fill(true, aliases?.Length ?? 0);
206213
InitializeTransformer(includeInTuple, GetIncludeInTransform(transformer, aliases, includeInTuple));
207214
}

0 commit comments

Comments
 (0)