From c59e60e5af875d3c8fa9ed56363b6f6ac01c5c82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <12201973+fredericdelaporte@users.noreply.github.com> Date: Sun, 2 Oct 2022 21:36:04 +0200 Subject: [PATCH 01/22] Support caching queries with autodiscovered types fix #3169 --- .../SqlTest/Query/NativeSQLQueriesFixture.cs | 116 ++++++++++-------- src/NHibernate/Cache/QueryAliasesKey.cs | 39 ++++++ src/NHibernate/Cache/StandardQueryCache.cs | 68 +++++++++- src/NHibernate/Loader/Loader.cs | 3 +- .../Transform/CacheableResultTransformer.cs | 7 ++ 5 files changed, 177 insertions(+), 56 deletions(-) create mode 100644 src/NHibernate/Cache/QueryAliasesKey.cs diff --git a/src/NHibernate.Test/SqlTest/Query/NativeSQLQueriesFixture.cs b/src/NHibernate.Test/SqlTest/Query/NativeSQLQueriesFixture.cs index 80eeb3c4c73..67304e2359d 100644 --- a/src/NHibernate.Test/SqlTest/Query/NativeSQLQueriesFixture.cs +++ b/src/NHibernate.Test/SqlTest/Query/NativeSQLQueriesFixture.cs @@ -1,8 +1,9 @@ using System.Collections; using System.Linq; +using NHibernate.Criterion; +using NHibernate.Multi; using NHibernate.Transform; using NUnit.Framework; -using NHibernate.Criterion; namespace NHibernate.Test.SqlTest.Query { @@ -51,6 +52,20 @@ protected override string MappingsAssembly get { return "NHibernate.Test"; } } + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from Employment").ExecuteUpdate(); + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + + transaction.Commit(); + } + + Sfi.QueryCache.Clear(); + } + [Test] public void FailOnNoAddEntityOrScalar() { @@ -125,18 +140,6 @@ public void SQLQueryInterface() t.Commit(); s.Close(); } - - using (var s = OpenSession()) - using (var t = s.BeginTransaction()) - { - s.Delete(emp); - s.Delete(gavin); - s.Delete(ifa); - s.Delete(jboss); - - t.Commit(); - s.Close(); - } } [Test] @@ -191,18 +194,6 @@ public void SQLQueryInterfaceCacheable() t.Commit(); s.Close(); } - - using (var s = OpenSession()) - using (var t = s.BeginTransaction()) - { - s.Delete(emp); - s.Delete(gavin); - s.Delete(ifa); - s.Delete(jboss); - - t.Commit(); - s.Close(); - } } [Test(Description = "GH-2904")] @@ -252,20 +243,11 @@ IList GetCacheableSqlQueryResults() Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "results are expected from cache"); } } - - using (var s = OpenSession()) - using (var t = s.BeginTransaction()) - { - s.Delete(emp); - s.Delete(gavin); - s.Delete(ifa); - s.Delete(jboss); - t.Commit(); - } } class ResultDto { + public long orgId { get; set; } public string regionCode { get; set; } } @@ -294,23 +276,64 @@ void AssertQuery(bool fromCache) .List(); t.Commit(); - Assert.AreEqual(1, l.Count); - //TODO: Uncomment if we properly fix caching auto discovery type queries with transformers - // var msg = "results are expected from " + (fromCache ? "cache" : "DB"); - // Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(fromCache ? 0 : 1), msg); - // Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(fromCache ? 1 : 0), msg); + Assert.That(l.Count, Is.EqualTo(1)); + var msg = "Results are expected from " + (fromCache ? "cache" : "DB"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(fromCache ? 0 : 1), msg); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(fromCache ? 1 : 0), msg); } } AssertQuery(false); AssertQuery(true); + } - using (var s = OpenSession()) - using (var t = s.BeginTransaction()) + [Test(Description = "GH-3169")] + public void CacheableScalarSQLMultiQueryWithTransformer() + { + Organization ifa = new Organization("IFA"); + + using (ISession s = OpenSession()) + using (ITransaction t = s.BeginTransaction()) { - s.Delete(ifa); + s.Save(ifa); t.Commit(); } + + void AssertQuery(bool fromCache) + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + using (EnableStatisticsScope()) + { + var q1 = s.CreateSQLQuery("select org.NAME as regionCode from ORGANIZATION org") + .AddScalar("regionCode", NHibernateUtil.String) + .SetResultTransformer(Transformers.AliasToBean()) + .SetCacheable(true); + var q2 = s.CreateSQLQuery("select org.ORGID as orgId from ORGANIZATION org") + .AddScalar("orgId", NHibernateUtil.Int64) + .SetResultTransformer(Transformers.AliasToBean()) + .SetCacheable(true); + + var batch = s.CreateQueryBatch(); + batch.Add(q1); + batch.Add(q2); + batch.Execute(); + + var l1 = batch.GetResult(0); + var l2 = batch.GetResult(1); + + t.Commit(); + + Assert.That(l1.Count, Is.EqualTo(1), "Unexpected results count for the first query."); + Assert.That(l2.Count, Is.EqualTo(1), "Unexpected results count for the second query."); + var msg = "Results are expected from " + (fromCache ? "cache" : "DB"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(fromCache ? 0 : 2), msg); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(fromCache ? 2 : 0), msg); + } + } + + AssertQuery(false); + AssertQuery(true); } [Test] @@ -338,11 +361,6 @@ public void ResultSetMappingDefinition() .List(); Assert.AreEqual(l.Count, 1); - s.Delete(emp); - s.Delete(gavin); - s.Delete(ifa); - s.Delete(jboss); - t.Commit(); s.Close(); } @@ -425,8 +443,6 @@ public void ScalarValues() Assert.AreEqual(o[1], "JBoss"); Assert.AreEqual(o[0], idJBoss); - s.Delete(ifa); - s.Delete(jboss); t.Commit(); s.Close(); } diff --git a/src/NHibernate/Cache/QueryAliasesKey.cs b/src/NHibernate/Cache/QueryAliasesKey.cs new file mode 100644 index 00000000000..7f1aad00c27 --- /dev/null +++ b/src/NHibernate/Cache/QueryAliasesKey.cs @@ -0,0 +1,39 @@ +using System; + +namespace NHibernate.Cache +{ + [Serializable] + public class QueryAliasesKey : IEquatable + { + private readonly QueryKey _queryKey; + + /// + /// Initializes a new instance of the class. + /// + /// The of the query for which aliases have to be stored. + public QueryAliasesKey(QueryKey queryKey) + { + _queryKey = queryKey ?? throw new ArgumentNullException(nameof(queryKey)); + } + + public override bool Equals(object other) + { + return Equals(other as QueryAliasesKey); + } + + public bool Equals(QueryAliasesKey other) + { + return other != null && _queryKey.Equals(other._queryKey); + } + + public override int GetHashCode() + { + return _queryKey.GetHashCode(); + } + + public override string ToString() + { + return "QueryAlisesKey: " + _queryKey.ToString(); + } + } +} diff --git a/src/NHibernate/Cache/StandardQueryCache.cs b/src/NHibernate/Cache/StandardQueryCache.cs index 8cda99f3815..5b869d123a1 100644 --- a/src/NHibernate/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Cache/StandardQueryCache.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using NHibernate.Cfg; -using NHibernate.Collection; using NHibernate.Engine; using NHibernate.Persister.Collection; using NHibernate.Type; @@ -86,8 +85,15 @@ public bool Put( { // 6.0 TODO: inline the call. #pragma warning disable 612 - return Put(key, returnTypes, result, queryParameters.NaturalKeyLookup, session); + var cached = Put(key, returnTypes, result, queryParameters.NaturalKeyLookup, session); #pragma warning restore 612 + + if (cached && key.ResultTransformer?.AutoDiscoverTypes == true && key.ResultTransformer.AutoDiscoveredAliases != null) + { + Cache.Put(new QueryAliasesKey(key), key.ResultTransformer.AutoDiscoveredAliases); + } + + return cached; } // Since 5.2 @@ -126,8 +132,22 @@ public IList Get( { // 6.0 TODO: inline the call. #pragma warning disable 612 - return Get(key, returnTypes, queryParameters.NaturalKeyLookup, spaces, session); + var result = Get(key, returnTypes, queryParameters.NaturalKeyLookup, spaces, session); #pragma warning restore 612 + + if (result != null && key.ResultTransformer?.AutoDiscoverTypes == true && result.Count > 0) + { + var aliasesKey = new QueryAliasesKey(key); + if (!(Cache.Get(aliasesKey) is string[] aliases)) + { + // Cannot properly initialize the result transformer, treat it as a cache miss + Log.Debug("query aliases were not found in cache: {0}", aliasesKey); + return null; + } + key.ResultTransformer.SupplyAutoDiscoveredParameters(queryParameters.ResultTransformer, aliases); + } + + return result; } finally { @@ -184,9 +204,16 @@ public bool[] PutMany( if (queryParameters[i].NaturalKeyLookup && result.Count == 0) continue; + var key = keys[i]; cached[i] = true; - cachedKeys.Add(keys[i]); + cachedKeys.Add(key); cachedResults.Add(GetCacheableResult(returnTypes[i], session, result, ts)); + + if (key.ResultTransformer?.AutoDiscoverTypes == true && key.ResultTransformer.AutoDiscoveredAliases != null) + { + cachedKeys.Add(new QueryAliasesKey(key)); + cachedResults.Add(key.ResultTransformer.AutoDiscoveredAliases); + } } _cache.PutMany(cachedKeys.ToArray(), cachedResults.ToArray()); @@ -244,6 +271,8 @@ public IList[] GetMany( { session.PersistenceContext.BatchFetchQueue.InitializeQueryCacheQueue(); + var queryAliasesKeys = new QueryAliasesKey[keys.Length]; + var hasAliasesToFetch = false; for (var i = 0; i < keys.Length; i++) { var cacheable = (IList) cacheables[i]; @@ -267,6 +296,37 @@ public IList[] GetMany( finalReturnTypes[i] = GetReturnTypes(key, returnTypes[i], cacheable); PerformBeforeAssemble(finalReturnTypes[i], session, cacheable); + + if (key.ResultTransformer?.AutoDiscoverTypes == true && cacheable.Count > 0) + { + queryAliasesKeys[i] = new QueryAliasesKey(key); + hasAliasesToFetch = true; + } + } + + if (hasAliasesToFetch) + { + var allAliases = _cache.GetMany(queryAliasesKeys.Where(k => k != null).ToArray()); + + var aliasesIndex = 0; + for (var i = 0; i < keys.Length; i++) + { + var queryAliasesKey = queryAliasesKeys[i]; + if (queryAliasesKey == null) + continue; + + if (allAliases[aliasesIndex] is string[] aliases) + { + keys[i].ResultTransformer.SupplyAutoDiscoveredParameters(queryParameters[i].ResultTransformer, aliases); + } + else + { + // Cannot properly initialize the result transformer, treat it as a cache miss + Log.Debug("query aliases were not found in cache: {0}", queryAliasesKey); + finalReturnTypes[i] = null; + } + aliasesIndex++; + } } for (var i = 0; i < keys.Length; i++) diff --git a/src/NHibernate/Loader/Loader.cs b/src/NHibernate/Loader/Loader.cs index 5005505c56d..bfb00ddf036 100644 --- a/src/NHibernate/Loader/Loader.cs +++ b/src/NHibernate/Loader/Loader.cs @@ -1842,8 +1842,7 @@ internal virtual bool IsCacheable(QueryParameters queryParameters) internal bool IsCacheable(QueryParameters queryParameters, bool supportsQueryCache, IEnumerable persisters) { - bool isCacheable = Factory.Settings.IsQueryCacheEnabled && queryParameters.Cacheable - && !(queryParameters.HasAutoDiscoverScalarTypes && queryParameters.ResultTransformer != null); + bool isCacheable = Factory.Settings.IsQueryCacheEnabled && queryParameters.Cacheable; if (isCacheable && !supportsQueryCache) { if (Factory.Settings.QueryThrowNeverCached) diff --git a/src/NHibernate/Transform/CacheableResultTransformer.cs b/src/NHibernate/Transform/CacheableResultTransformer.cs index e3dba66d028..3f732f20165 100644 --- a/src/NHibernate/Transform/CacheableResultTransformer.cs +++ b/src/NHibernate/Transform/CacheableResultTransformer.cs @@ -23,6 +23,11 @@ public class CacheableResultTransformer : IResultTransformer public bool AutoDiscoverTypes { get; } + /// + /// The auto-discovered aliaises. + /// + public string[] AutoDiscoveredAliases { get; private set; } + private readonly SqlString _autoDiscoveredQuery; private readonly bool _skipTransformer; private int _tupleLength; @@ -202,6 +207,8 @@ internal void SupplyAutoDiscoveredParameters(IResultTransformer transformer, str throw new InvalidOperationException( "Cannot supply auto-discovered parameters when it is not enabled on the transformer."); + AutoDiscoveredAliases = aliases; + var includeInTuple = ArrayHelper.Fill(true, aliases?.Length ?? 0); InitializeTransformer(includeInTuple, GetIncludeInTransform(transformer, aliases, includeInTuple)); } From 94aa1de94783890563779b961ee8ad14f40ce984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <12201973+fredericdelaporte@users.noreply.github.com> Date: Sun, 16 Oct 2022 19:34:53 +0200 Subject: [PATCH 02/22] Fix a typo --- src/NHibernate/Transform/CacheableResultTransformer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NHibernate/Transform/CacheableResultTransformer.cs b/src/NHibernate/Transform/CacheableResultTransformer.cs index 3f732f20165..1f05747082e 100644 --- a/src/NHibernate/Transform/CacheableResultTransformer.cs +++ b/src/NHibernate/Transform/CacheableResultTransformer.cs @@ -24,7 +24,7 @@ public class CacheableResultTransformer : IResultTransformer public bool AutoDiscoverTypes { get; } /// - /// The auto-discovered aliaises. + /// The auto-discovered aliases. /// public string[] AutoDiscoveredAliases { get; private set; } From 0bf792157f295288fc7c37edbe256eed0198491b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 16 Oct 2022 17:38:00 +0000 Subject: [PATCH 03/22] Generate async files --- .../SqlTest/Query/NativeSQLQueriesFixture.cs | 116 ++++++++++-------- .../Async/Cache/StandardQueryCache.cs | 75 +++++++++-- 2 files changed, 132 insertions(+), 59 deletions(-) diff --git a/src/NHibernate.Test/Async/SqlTest/Query/NativeSQLQueriesFixture.cs b/src/NHibernate.Test/Async/SqlTest/Query/NativeSQLQueriesFixture.cs index 5ef4ebad9d8..2127d251b4d 100644 --- a/src/NHibernate.Test/Async/SqlTest/Query/NativeSQLQueriesFixture.cs +++ b/src/NHibernate.Test/Async/SqlTest/Query/NativeSQLQueriesFixture.cs @@ -10,9 +10,10 @@ using System.Collections; using System.Linq; +using NHibernate.Criterion; +using NHibernate.Multi; using NHibernate.Transform; using NUnit.Framework; -using NHibernate.Criterion; namespace NHibernate.Test.SqlTest.Query { @@ -62,6 +63,20 @@ protected override string MappingsAssembly get { return "NHibernate.Test"; } } + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from Employment").ExecuteUpdate(); + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + + transaction.Commit(); + } + + Sfi.QueryCache.Clear(); + } + [Test] public async Task FailOnNoAddEntityOrScalarAsync() { @@ -136,18 +151,6 @@ public async Task SQLQueryInterfaceAsync() await (t.CommitAsync()); s.Close(); } - - using (var s = OpenSession()) - using (var t = s.BeginTransaction()) - { - await (s.DeleteAsync(emp)); - await (s.DeleteAsync(gavin)); - await (s.DeleteAsync(ifa)); - await (s.DeleteAsync(jboss)); - - await (t.CommitAsync()); - s.Close(); - } } [Test] @@ -202,18 +205,6 @@ public async Task SQLQueryInterfaceCacheableAsync() await (t.CommitAsync()); s.Close(); } - - using (var s = OpenSession()) - using (var t = s.BeginTransaction()) - { - await (s.DeleteAsync(emp)); - await (s.DeleteAsync(gavin)); - await (s.DeleteAsync(ifa)); - await (s.DeleteAsync(jboss)); - - await (t.CommitAsync()); - s.Close(); - } } [Test(Description = "GH-2904")] @@ -270,20 +261,11 @@ Task GetCacheableSqlQueryResultsAsync() Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "results are expected from cache"); } } - - using (var s = OpenSession()) - using (var t = s.BeginTransaction()) - { - await (s.DeleteAsync(emp)); - await (s.DeleteAsync(gavin)); - await (s.DeleteAsync(ifa)); - await (s.DeleteAsync(jboss)); - await (t.CommitAsync()); - } } class ResultDto { + public long orgId { get; set; } public string regionCode { get; set; } } @@ -312,23 +294,64 @@ async Task AssertQueryAsync(bool fromCache) .ListAsync()); await (t.CommitAsync()); - Assert.AreEqual(1, l.Count); - //TODO: Uncomment if we properly fix caching auto discovery type queries with transformers - // var msg = "results are expected from " + (fromCache ? "cache" : "DB"); - // Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(fromCache ? 0 : 1), msg); - // Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(fromCache ? 1 : 0), msg); + Assert.That(l.Count, Is.EqualTo(1)); + var msg = "Results are expected from " + (fromCache ? "cache" : "DB"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(fromCache ? 0 : 1), msg); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(fromCache ? 1 : 0), msg); } } await (AssertQueryAsync(false)); await (AssertQueryAsync(true)); + } - using (var s = OpenSession()) - using (var t = s.BeginTransaction()) + [Test(Description = "GH-3169")] + public async Task CacheableScalarSQLMultiQueryWithTransformerAsync() + { + Organization ifa = new Organization("IFA"); + + using (ISession s = OpenSession()) + using (ITransaction t = s.BeginTransaction()) { - await (s.DeleteAsync(ifa)); + await (s.SaveAsync(ifa)); await (t.CommitAsync()); } + + async Task AssertQueryAsync(bool fromCache) + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + using (EnableStatisticsScope()) + { + var q1 = s.CreateSQLQuery("select org.NAME as regionCode from ORGANIZATION org") + .AddScalar("regionCode", NHibernateUtil.String) + .SetResultTransformer(Transformers.AliasToBean()) + .SetCacheable(true); + var q2 = s.CreateSQLQuery("select org.ORGID as orgId from ORGANIZATION org") + .AddScalar("orgId", NHibernateUtil.Int64) + .SetResultTransformer(Transformers.AliasToBean()) + .SetCacheable(true); + + var batch = s.CreateQueryBatch(); + batch.Add(q1); + batch.Add(q2); + await (batch.ExecuteAsync()); + + var l1 = await (batch.GetResultAsync(0)); + var l2 = await (batch.GetResultAsync(1)); + + await (t.CommitAsync()); + + Assert.That(l1.Count, Is.EqualTo(1), "Unexpected results count for the first query."); + Assert.That(l2.Count, Is.EqualTo(1), "Unexpected results count for the second query."); + var msg = "Results are expected from " + (fromCache ? "cache" : "DB"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(fromCache ? 0 : 2), msg); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(fromCache ? 2 : 0), msg); + } + } + + await (AssertQueryAsync(false)); + await (AssertQueryAsync(true)); } [Test] @@ -356,11 +379,6 @@ public async Task ResultSetMappingDefinitionAsync() .ListAsync()); Assert.AreEqual(l.Count, 1); - await (s.DeleteAsync(emp)); - await (s.DeleteAsync(gavin)); - await (s.DeleteAsync(ifa)); - await (s.DeleteAsync(jboss)); - await (t.CommitAsync()); s.Close(); } @@ -443,8 +461,6 @@ public async Task ScalarValuesAsync() Assert.AreEqual(o[1], "JBoss"); Assert.AreEqual(o[0], idJBoss); - await (s.DeleteAsync(ifa)); - await (s.DeleteAsync(jboss)); await (t.CommitAsync()); s.Close(); } diff --git a/src/NHibernate/Async/Cache/StandardQueryCache.cs b/src/NHibernate/Async/Cache/StandardQueryCache.cs index ab7d4f4547e..c062cdfe7f1 100644 --- a/src/NHibernate/Async/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Async/Cache/StandardQueryCache.cs @@ -13,7 +13,6 @@ using System.Collections.Generic; using System.Linq; using NHibernate.Cfg; -using NHibernate.Collection; using NHibernate.Engine; using NHibernate.Persister.Collection; using NHibernate.Type; @@ -38,21 +37,25 @@ public Task ClearAsync(CancellationToken cancellationToken) } /// - public Task PutAsync( + public async Task PutAsync( QueryKey key, QueryParameters queryParameters, ICacheAssembler[] returnTypes, IList result, ISessionImplementor session, CancellationToken cancellationToken) { - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } + cancellationToken.ThrowIfCancellationRequested(); // 6.0 TODO: inline the call. #pragma warning disable 612 - return PutAsync(key, returnTypes, result, queryParameters.NaturalKeyLookup, session, cancellationToken); + var cached = await (PutAsync(key, returnTypes, result, queryParameters.NaturalKeyLookup, session, cancellationToken)).ConfigureAwait(false); #pragma warning restore 612 + + if (cached && key.ResultTransformer?.AutoDiscoverTypes == true && key.ResultTransformer.AutoDiscoveredAliases != null) + { + await (Cache.PutAsync(new QueryAliasesKey(key), key.ResultTransformer.AutoDiscoveredAliases, cancellationToken)).ConfigureAwait(false); + } + + return cached; } // Since 5.2 @@ -93,8 +96,22 @@ public async Task GetAsync( { // 6.0 TODO: inline the call. #pragma warning disable 612 - return await (GetAsync(key, returnTypes, queryParameters.NaturalKeyLookup, spaces, session, cancellationToken)).ConfigureAwait(false); + var result = await (GetAsync(key, returnTypes, queryParameters.NaturalKeyLookup, spaces, session, cancellationToken)).ConfigureAwait(false); #pragma warning restore 612 + + if (result != null && key.ResultTransformer?.AutoDiscoverTypes == true && result.Count > 0) + { + var aliasesKey = new QueryAliasesKey(key); + if (!(await (Cache.GetAsync(aliasesKey, cancellationToken)).ConfigureAwait(false) is string[] aliases)) + { + // Cannot properly initialize the result transformer, treat it as a cache miss + Log.Debug("query aliases were not found in cache: {0}", aliasesKey); + return null; + } + key.ResultTransformer.SupplyAutoDiscoveredParameters(queryParameters.ResultTransformer, aliases); + } + + return result; } finally { @@ -153,9 +170,16 @@ public async Task PutManyAsync( if (queryParameters[i].NaturalKeyLookup && result.Count == 0) continue; + var key = keys[i]; cached[i] = true; - cachedKeys.Add(keys[i]); + cachedKeys.Add(key); cachedResults.Add(await (GetCacheableResultAsync(returnTypes[i], session, result, ts, cancellationToken)).ConfigureAwait(false)); + + if (key.ResultTransformer?.AutoDiscoverTypes == true && key.ResultTransformer.AutoDiscoveredAliases != null) + { + cachedKeys.Add(new QueryAliasesKey(key)); + cachedResults.Add(key.ResultTransformer.AutoDiscoveredAliases); + } } await (_cache.PutManyAsync(cachedKeys.ToArray(), cachedResults.ToArray(), cancellationToken)).ConfigureAwait(false); @@ -214,6 +238,8 @@ public async Task GetManyAsync( { session.PersistenceContext.BatchFetchQueue.InitializeQueryCacheQueue(); + var queryAliasesKeys = new QueryAliasesKey[keys.Length]; + var hasAliasesToFetch = false; for (var i = 0; i < keys.Length; i++) { var cacheable = (IList) cacheables[i]; @@ -237,6 +263,37 @@ public async Task GetManyAsync( finalReturnTypes[i] = GetReturnTypes(key, returnTypes[i], cacheable); await (PerformBeforeAssembleAsync(finalReturnTypes[i], session, cacheable, cancellationToken)).ConfigureAwait(false); + + if (key.ResultTransformer?.AutoDiscoverTypes == true && cacheable.Count > 0) + { + queryAliasesKeys[i] = new QueryAliasesKey(key); + hasAliasesToFetch = true; + } + } + + if (hasAliasesToFetch) + { + var allAliases = await (_cache.GetManyAsync(queryAliasesKeys.Where(k => k != null).ToArray(), cancellationToken)).ConfigureAwait(false); + + var aliasesIndex = 0; + for (var i = 0; i < keys.Length; i++) + { + var queryAliasesKey = queryAliasesKeys[i]; + if (queryAliasesKey == null) + continue; + + if (allAliases[aliasesIndex] is string[] aliases) + { + keys[i].ResultTransformer.SupplyAutoDiscoveredParameters(queryParameters[i].ResultTransformer, aliases); + } + else + { + // Cannot properly initialize the result transformer, treat it as a cache miss + Log.Debug("query aliases were not found in cache: {0}", queryAliasesKey); + finalReturnTypes[i] = null; + } + aliasesIndex++; + } } for (var i = 0; i < keys.Length; i++) From 261ac75da36f111d0d9440dd33994269e2beae2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <12201973+fredericdelaporte@users.noreply.github.com> Date: Sun, 16 Oct 2022 22:29:04 +0200 Subject: [PATCH 04/22] Add the aliases directly into cached data --- src/NHibernate/Cache/QueryAliasesKey.cs | 39 ------ src/NHibernate/Cache/StandardQueryCache.cs | 156 ++++++++++----------- 2 files changed, 73 insertions(+), 122 deletions(-) delete mode 100644 src/NHibernate/Cache/QueryAliasesKey.cs diff --git a/src/NHibernate/Cache/QueryAliasesKey.cs b/src/NHibernate/Cache/QueryAliasesKey.cs deleted file mode 100644 index 7f1aad00c27..00000000000 --- a/src/NHibernate/Cache/QueryAliasesKey.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; - -namespace NHibernate.Cache -{ - [Serializable] - public class QueryAliasesKey : IEquatable - { - private readonly QueryKey _queryKey; - - /// - /// Initializes a new instance of the class. - /// - /// The of the query for which aliases have to be stored. - public QueryAliasesKey(QueryKey queryKey) - { - _queryKey = queryKey ?? throw new ArgumentNullException(nameof(queryKey)); - } - - public override bool Equals(object other) - { - return Equals(other as QueryAliasesKey); - } - - public bool Equals(QueryAliasesKey other) - { - return other != null && _queryKey.Equals(other._queryKey); - } - - public override int GetHashCode() - { - return _queryKey.GetHashCode(); - } - - public override string ToString() - { - return "QueryAlisesKey: " + _queryKey.ToString(); - } - } -} diff --git a/src/NHibernate/Cache/StandardQueryCache.cs b/src/NHibernate/Cache/StandardQueryCache.cs index 5b869d123a1..76814a4fad1 100644 --- a/src/NHibernate/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Cache/StandardQueryCache.cs @@ -83,17 +83,16 @@ public bool Put( IList result, ISessionImplementor session) { - // 6.0 TODO: inline the call. -#pragma warning disable 612 - var cached = Put(key, returnTypes, result, queryParameters.NaturalKeyLookup, session); -#pragma warning restore 612 + if (queryParameters.NaturalKeyLookup && result.Count == 0) + return false; - if (cached && key.ResultTransformer?.AutoDiscoverTypes == true && key.ResultTransformer.AutoDiscoveredAliases != null) - { - Cache.Put(new QueryAliasesKey(key), key.ResultTransformer.AutoDiscoveredAliases); - } + var ts = session.Factory.Settings.CacheProvider.NextTimestamp(); - return cached; + Log.Debug("caching query results in region: '{0}'; {1}", _regionName, key); + + Cache.Put(key, GetCacheableResult(returnTypes, session, result, ts, GetAutoDiscoveredAliases(key))); + + return true; } // Since 5.2 @@ -107,7 +106,7 @@ public bool Put(QueryKey key, ICacheAssembler[] returnTypes, IList result, bool Log.Debug("caching query results in region: '{0}'; {1}", _regionName, key); - Cache.Put(key, GetCacheableResult(returnTypes, session, result, ts)); + Cache.Put(key, GetCacheableResult(returnTypes, session, result, ts, null)); return true; } @@ -130,20 +129,32 @@ public IList Get( try { - // 6.0 TODO: inline the call. -#pragma warning disable 612 - var result = Get(key, returnTypes, queryParameters.NaturalKeyLookup, spaces, session); -#pragma warning restore 612 + if (Log.IsDebugEnabled()) + Log.Debug("checking cached query results in region: '{0}'; {1}", _regionName, key); + + var cacheable = (IList) Cache.Get(key); + if (cacheable == null) + { + Log.Debug("query results were not found in cache: {0}", key); + return null; + } + + var timestamp = (long) cacheable[0]; + + if (Log.IsDebugEnabled()) + Log.Debug("Checking query spaces for up-to-dateness [{0}]", StringHelper.CollectionToString(spaces)); + + if (!queryParameters.NaturalKeyLookup && !IsUpToDate(spaces, timestamp)) + { + Log.Debug("cached query results were not up to date for: {0}", key); + return null; + } + + var result = GetResultFromCacheable(key, returnTypes, queryParameters.NaturalKeyLookup, session, cacheable); if (result != null && key.ResultTransformer?.AutoDiscoverTypes == true && result.Count > 0) { - var aliasesKey = new QueryAliasesKey(key); - if (!(Cache.Get(aliasesKey) is string[] aliases)) - { - // Cannot properly initialize the result transformer, treat it as a cache miss - Log.Debug("query aliases were not found in cache: {0}", aliasesKey); - return null; - } + var aliases = (string[]) cacheable[1]; key.ResultTransformer.SupplyAutoDiscoveredParameters(queryParameters.ResultTransformer, aliases); } @@ -207,13 +218,7 @@ public bool[] PutMany( var key = keys[i]; cached[i] = true; cachedKeys.Add(key); - cachedResults.Add(GetCacheableResult(returnTypes[i], session, result, ts)); - - if (key.ResultTransformer?.AutoDiscoverTypes == true && key.ResultTransformer.AutoDiscoveredAliases != null) - { - cachedKeys.Add(new QueryAliasesKey(key)); - cachedResults.Add(key.ResultTransformer.AutoDiscoveredAliases); - } + cachedResults.Add(GetCacheableResult(returnTypes[i], session, result, ts, GetAutoDiscoveredAliases(key))); } _cache.PutMany(cachedKeys.ToArray(), cachedResults.ToArray()); @@ -221,6 +226,11 @@ public bool[] PutMany( return cached; } + private static string[] GetAutoDiscoveredAliases(QueryKey key) + { + return key.ResultTransformer?.AutoDiscoverTypes == true ? key.ResultTransformer.AutoDiscoveredAliases : null; + } + /// public IList[] GetMany( QueryKey[] keys, @@ -271,8 +281,6 @@ public IList[] GetMany( { session.PersistenceContext.BatchFetchQueue.InitializeQueryCacheQueue(); - var queryAliasesKeys = new QueryAliasesKey[keys.Length]; - var hasAliasesToFetch = false; for (var i = 0; i < keys.Length; i++) { var cacheable = (IList) cacheables[i]; @@ -297,35 +305,10 @@ public IList[] GetMany( finalReturnTypes[i] = GetReturnTypes(key, returnTypes[i], cacheable); PerformBeforeAssemble(finalReturnTypes[i], session, cacheable); - if (key.ResultTransformer?.AutoDiscoverTypes == true && cacheable.Count > 0) + if (key.ResultTransformer?.AutoDiscoverTypes == true && cacheable.Count > 2) { - queryAliasesKeys[i] = new QueryAliasesKey(key); - hasAliasesToFetch = true; - } - } - - if (hasAliasesToFetch) - { - var allAliases = _cache.GetMany(queryAliasesKeys.Where(k => k != null).ToArray()); - - var aliasesIndex = 0; - for (var i = 0; i < keys.Length; i++) - { - var queryAliasesKey = queryAliasesKeys[i]; - if (queryAliasesKey == null) - continue; - - if (allAliases[aliasesIndex] is string[] aliases) - { - keys[i].ResultTransformer.SupplyAutoDiscoveredParameters(queryParameters[i].ResultTransformer, aliases); - } - else - { - // Cannot properly initialize the result transformer, treat it as a cache miss - Log.Debug("query aliases were not found in cache: {0}", queryAliasesKey); - finalReturnTypes[i] = null; - } - aliasesIndex++; + var aliases = (string[]) cacheable[1]; + key.ResultTransformer.SupplyAutoDiscoveredParameters(queryParams.ResultTransformer, aliases); } } @@ -395,9 +378,9 @@ private static List GetCacheableResult( ICacheAssembler[] returnTypes, ISessionImplementor session, IList result, - long ts) + long ts, string[] aliases) { - var cacheable = new List(result.Count + 1) { ts }; + var cacheable = new List(result.Count + 2) { ts, aliases }; foreach (var row in result) { if (returnTypes.Length == 1) @@ -413,12 +396,21 @@ private static List GetCacheableResult( return cacheable; } + private static IEnumerable GetResultsEnumerable(IList results) + { + // Skip first element, it is the timestamp, and the second, it is the aliases. + for (var i = 2; i < results.Count; i++) + { + yield return results[i]; + } + } + private static ICacheAssembler[] GetReturnTypes( QueryKey key, ICacheAssembler[] returnTypes, IList cacheable) { - if (key.ResultTransformer?.AutoDiscoverTypes == true && cacheable.Count > 0) + if (key.ResultTransformer?.AutoDiscoverTypes == true && cacheable.Count > 2) { returnTypes = GuessTypes(cacheable); } @@ -435,18 +427,16 @@ private static void PerformBeforeAssemble( { var returnType = returnTypes[0]; - // Skip first element, it is the timestamp - for (var i = 1; i < cacheable.Count; i++) + foreach (var cached in GetResultsEnumerable(cacheable)) { - returnType.BeforeAssemble(cacheable[i], session); + returnType.BeforeAssemble(cached, session); } } else { - // Skip first element, it is the timestamp - for (var i = 1; i < cacheable.Count; i++) + foreach (var cached in GetResultsEnumerable(cacheable)) { - TypeHelper.BeforeAssemble((object[]) cacheable[i], returnTypes, session); + TypeHelper.BeforeAssemble((object[]) cached, returnTypes, session); } } } @@ -460,15 +450,14 @@ private IList PerformAssemble( { try { - var result = new List(cacheable.Count - 1); + var result = new List(cacheable.Count - 2); if (returnTypes.Length == 1) { var returnType = returnTypes[0]; - // Skip first element, it is the timestamp - for (var i = 1; i < cacheable.Count; i++) + foreach (var cached in GetResultsEnumerable(cacheable)) { - result.Add(returnType.Assemble(cacheable[i], session, null)); + result.Add(returnType.Assemble(cached, session, null)); } } else @@ -482,10 +471,9 @@ private IList PerformAssemble( } } - // Skip first element, it is the timestamp - for (var i = 1; i < cacheable.Count; i++) + foreach (var cached in GetResultsEnumerable(cacheable)) { - result.Add(TypeHelper.Assemble((object[]) cacheable[i], returnTypes, nonCollectionTypeIndexes, session)); + result.Add(TypeHelper.Assemble((object[]) cached, returnTypes, nonCollectionTypeIndexes, session)); } } @@ -530,17 +518,18 @@ private static void InitializeCollections( return; } - // Skip first element, it is the timestamp - for (var i = 1; i < cacheResult.Count; i++) + var j = 0; + foreach (var cached in GetResultsEnumerable(cacheResult)) { // Initialization of the fetched collection must be done at the end in order to be able to batch fetch them // from the cache or database. The collections were already created when their owners were assembled so we only // have to initialize them. TypeHelper.InitializeCollections( - (object[]) cacheResult[i], - (object[]) assembleResult[i - 1], + (object[]) cached, + (object[]) assembleResult[j], collectionIndexes, session); + j++; } } @@ -570,23 +559,24 @@ private IList GetResultFromCacheable( private static ICacheAssembler[] GuessTypes(IList cacheable) { - var colCount = (cacheable[0] as object[])?.Length ?? 1; + var colCount = (cacheable[2] as object[])?.Length ?? 1; var returnTypes = new ICacheAssembler[colCount]; if (colCount == 1) { - foreach (var obj in cacheable) + foreach (var cached in GetResultsEnumerable(cacheable)) { - if (obj == null) + if (cached == null) continue; - returnTypes[0] = NHibernateUtil.GuessType(obj); + returnTypes[0] = NHibernateUtil.GuessType(cached); break; } } else { var foundTypes = 0; - foreach (object[] row in cacheable) + foreach (var cached in GetResultsEnumerable(cacheable)) { + var row = (object[]) cached; for (var i = 0; i < colCount; i++) { if (row[i] != null && returnTypes[i] == null) From 3f28ad90603b0bf6777b5cc037cb2a72ed01ed48 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 16 Oct 2022 20:31:49 +0000 Subject: [PATCH 05/22] Generate async files --- .../Async/Cache/StandardQueryCache.cs | 129 +++++++----------- 1 file changed, 52 insertions(+), 77 deletions(-) diff --git a/src/NHibernate/Async/Cache/StandardQueryCache.cs b/src/NHibernate/Async/Cache/StandardQueryCache.cs index c062cdfe7f1..971a4332a89 100644 --- a/src/NHibernate/Async/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Async/Cache/StandardQueryCache.cs @@ -45,17 +45,16 @@ public async Task PutAsync( ISessionImplementor session, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - // 6.0 TODO: inline the call. -#pragma warning disable 612 - var cached = await (PutAsync(key, returnTypes, result, queryParameters.NaturalKeyLookup, session, cancellationToken)).ConfigureAwait(false); -#pragma warning restore 612 + if (queryParameters.NaturalKeyLookup && result.Count == 0) + return false; - if (cached && key.ResultTransformer?.AutoDiscoverTypes == true && key.ResultTransformer.AutoDiscoveredAliases != null) - { - await (Cache.PutAsync(new QueryAliasesKey(key), key.ResultTransformer.AutoDiscoveredAliases, cancellationToken)).ConfigureAwait(false); - } + var ts = session.Factory.Settings.CacheProvider.NextTimestamp(); - return cached; + Log.Debug("caching query results in region: '{0}'; {1}", _regionName, key); + + await (Cache.PutAsync(key, await (GetCacheableResultAsync(returnTypes, session, result, ts, GetAutoDiscoveredAliases(key), cancellationToken)).ConfigureAwait(false), cancellationToken)).ConfigureAwait(false); + + return true; } // Since 5.2 @@ -70,7 +69,7 @@ public async Task PutAsync(QueryKey key, ICacheAssembler[] returnTypes, IL Log.Debug("caching query results in region: '{0}'; {1}", _regionName, key); - await (Cache.PutAsync(key, await (GetCacheableResultAsync(returnTypes, session, result, ts, cancellationToken)).ConfigureAwait(false), cancellationToken)).ConfigureAwait(false); + await (Cache.PutAsync(key, await (GetCacheableResultAsync(returnTypes, session, result, ts, null, cancellationToken)).ConfigureAwait(false), cancellationToken)).ConfigureAwait(false); return true; } @@ -94,20 +93,32 @@ public async Task GetAsync( try { - // 6.0 TODO: inline the call. -#pragma warning disable 612 - var result = await (GetAsync(key, returnTypes, queryParameters.NaturalKeyLookup, spaces, session, cancellationToken)).ConfigureAwait(false); -#pragma warning restore 612 + if (Log.IsDebugEnabled()) + Log.Debug("checking cached query results in region: '{0}'; {1}", _regionName, key); + + var cacheable = (IList) await (Cache.GetAsync(key, cancellationToken)).ConfigureAwait(false); + if (cacheable == null) + { + Log.Debug("query results were not found in cache: {0}", key); + return null; + } + + var timestamp = (long) cacheable[0]; + + if (Log.IsDebugEnabled()) + Log.Debug("Checking query spaces for up-to-dateness [{0}]", StringHelper.CollectionToString(spaces)); + + if (!queryParameters.NaturalKeyLookup && !await (IsUpToDateAsync(spaces, timestamp, cancellationToken)).ConfigureAwait(false)) + { + Log.Debug("cached query results were not up to date for: {0}", key); + return null; + } + + var result = await (GetResultFromCacheableAsync(key, returnTypes, queryParameters.NaturalKeyLookup, session, cacheable, cancellationToken)).ConfigureAwait(false); if (result != null && key.ResultTransformer?.AutoDiscoverTypes == true && result.Count > 0) { - var aliasesKey = new QueryAliasesKey(key); - if (!(await (Cache.GetAsync(aliasesKey, cancellationToken)).ConfigureAwait(false) is string[] aliases)) - { - // Cannot properly initialize the result transformer, treat it as a cache miss - Log.Debug("query aliases were not found in cache: {0}", aliasesKey); - return null; - } + var aliases = (string[]) cacheable[1]; key.ResultTransformer.SupplyAutoDiscoveredParameters(queryParameters.ResultTransformer, aliases); } @@ -173,13 +184,7 @@ public async Task PutManyAsync( var key = keys[i]; cached[i] = true; cachedKeys.Add(key); - cachedResults.Add(await (GetCacheableResultAsync(returnTypes[i], session, result, ts, cancellationToken)).ConfigureAwait(false)); - - if (key.ResultTransformer?.AutoDiscoverTypes == true && key.ResultTransformer.AutoDiscoveredAliases != null) - { - cachedKeys.Add(new QueryAliasesKey(key)); - cachedResults.Add(key.ResultTransformer.AutoDiscoveredAliases); - } + cachedResults.Add(await (GetCacheableResultAsync(returnTypes[i], session, result, ts, GetAutoDiscoveredAliases(key), cancellationToken)).ConfigureAwait(false)); } await (_cache.PutManyAsync(cachedKeys.ToArray(), cachedResults.ToArray(), cancellationToken)).ConfigureAwait(false); @@ -238,8 +243,6 @@ public async Task GetManyAsync( { session.PersistenceContext.BatchFetchQueue.InitializeQueryCacheQueue(); - var queryAliasesKeys = new QueryAliasesKey[keys.Length]; - var hasAliasesToFetch = false; for (var i = 0; i < keys.Length; i++) { var cacheable = (IList) cacheables[i]; @@ -264,35 +267,10 @@ public async Task GetManyAsync( finalReturnTypes[i] = GetReturnTypes(key, returnTypes[i], cacheable); await (PerformBeforeAssembleAsync(finalReturnTypes[i], session, cacheable, cancellationToken)).ConfigureAwait(false); - if (key.ResultTransformer?.AutoDiscoverTypes == true && cacheable.Count > 0) + if (key.ResultTransformer?.AutoDiscoverTypes == true && cacheable.Count > 2) { - queryAliasesKeys[i] = new QueryAliasesKey(key); - hasAliasesToFetch = true; - } - } - - if (hasAliasesToFetch) - { - var allAliases = await (_cache.GetManyAsync(queryAliasesKeys.Where(k => k != null).ToArray(), cancellationToken)).ConfigureAwait(false); - - var aliasesIndex = 0; - for (var i = 0; i < keys.Length; i++) - { - var queryAliasesKey = queryAliasesKeys[i]; - if (queryAliasesKey == null) - continue; - - if (allAliases[aliasesIndex] is string[] aliases) - { - keys[i].ResultTransformer.SupplyAutoDiscoveredParameters(queryParameters[i].ResultTransformer, aliases); - } - else - { - // Cannot properly initialize the result transformer, treat it as a cache miss - Log.Debug("query aliases were not found in cache: {0}", queryAliasesKey); - finalReturnTypes[i] = null; - } - aliasesIndex++; + var aliases = (string[]) cacheable[1]; + key.ResultTransformer.SupplyAutoDiscoveredParameters(queryParams.ResultTransformer, aliases); } } @@ -356,10 +334,10 @@ private static async Task> GetCacheableResultAsync( ICacheAssembler[] returnTypes, ISessionImplementor session, IList result, - long ts, CancellationToken cancellationToken) + long ts, string[] aliases, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - var cacheable = new List(result.Count + 1) { ts }; + var cacheable = new List(result.Count + 2) { ts, aliases }; foreach (var row in result) { if (returnTypes.Length == 1) @@ -385,18 +363,16 @@ private static async Task PerformBeforeAssembleAsync( { var returnType = returnTypes[0]; - // Skip first element, it is the timestamp - for (var i = 1; i < cacheable.Count; i++) + foreach (var cached in GetResultsEnumerable(cacheable)) { - await (returnType.BeforeAssembleAsync(cacheable[i], session, cancellationToken)).ConfigureAwait(false); + await (returnType.BeforeAssembleAsync(cached, session, cancellationToken)).ConfigureAwait(false); } } else { - // Skip first element, it is the timestamp - for (var i = 1; i < cacheable.Count; i++) + foreach (var cached in GetResultsEnumerable(cacheable)) { - await (TypeHelper.BeforeAssembleAsync((object[]) cacheable[i], returnTypes, session, cancellationToken)).ConfigureAwait(false); + await (TypeHelper.BeforeAssembleAsync((object[]) cached, returnTypes, session, cancellationToken)).ConfigureAwait(false); } } } @@ -411,15 +387,14 @@ private async Task PerformAssembleAsync( cancellationToken.ThrowIfCancellationRequested(); try { - var result = new List(cacheable.Count - 1); + var result = new List(cacheable.Count - 2); if (returnTypes.Length == 1) { var returnType = returnTypes[0]; - // Skip first element, it is the timestamp - for (var i = 1; i < cacheable.Count; i++) + foreach (var cached in GetResultsEnumerable(cacheable)) { - result.Add(await (returnType.AssembleAsync(cacheable[i], session, null, cancellationToken)).ConfigureAwait(false)); + result.Add(await (returnType.AssembleAsync(cached, session, null, cancellationToken)).ConfigureAwait(false)); } } else @@ -433,10 +408,9 @@ private async Task PerformAssembleAsync( } } - // Skip first element, it is the timestamp - for (var i = 1; i < cacheable.Count; i++) + foreach (var cached in GetResultsEnumerable(cacheable)) { - result.Add(await (TypeHelper.AssembleAsync((object[]) cacheable[i], returnTypes, nonCollectionTypeIndexes, session, cancellationToken)).ConfigureAwait(false)); + result.Add(await (TypeHelper.AssembleAsync((object[]) cached, returnTypes, nonCollectionTypeIndexes, session, cancellationToken)).ConfigureAwait(false)); } } @@ -482,17 +456,18 @@ private static async Task InitializeCollectionsAsync( return; } - // Skip first element, it is the timestamp - for (var i = 1; i < cacheResult.Count; i++) + var j = 0; + foreach (var cached in GetResultsEnumerable(cacheResult)) { // Initialization of the fetched collection must be done at the end in order to be able to batch fetch them // from the cache or database. The collections were already created when their owners were assembled so we only // have to initialize them. await (TypeHelper.InitializeCollectionsAsync( - (object[]) cacheResult[i], - (object[]) assembleResult[i - 1], + (object[]) cached, + (object[]) assembleResult[j], collectionIndexes, session, cancellationToken)).ConfigureAwait(false); + j++; } } From 8228b04db4cf37dae4757a884681fb521bf37307 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <12201973+fredericdelaporte@users.noreply.github.com> Date: Sun, 16 Oct 2022 22:36:09 +0200 Subject: [PATCH 06/22] Minimize the change --- src/NHibernate/Cache/StandardQueryCache.cs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/NHibernate/Cache/StandardQueryCache.cs b/src/NHibernate/Cache/StandardQueryCache.cs index 76814a4fad1..756a55f1436 100644 --- a/src/NHibernate/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Cache/StandardQueryCache.cs @@ -83,16 +83,10 @@ public bool Put( IList result, ISessionImplementor session) { - if (queryParameters.NaturalKeyLookup && result.Count == 0) - return false; - - var ts = session.Factory.Settings.CacheProvider.NextTimestamp(); - - Log.Debug("caching query results in region: '{0}'; {1}", _regionName, key); - - Cache.Put(key, GetCacheableResult(returnTypes, session, result, ts, GetAutoDiscoveredAliases(key))); - - return true; + // 6.0 TODO: inline the call. +#pragma warning disable 612 + return Put(key, returnTypes, result, queryParameters.NaturalKeyLookup, session); +#pragma warning restore 612 } // Since 5.2 @@ -106,7 +100,7 @@ public bool Put(QueryKey key, ICacheAssembler[] returnTypes, IList result, bool Log.Debug("caching query results in region: '{0}'; {1}", _regionName, key); - Cache.Put(key, GetCacheableResult(returnTypes, session, result, ts, null)); + Cache.Put(key, GetCacheableResult(returnTypes, session, result, ts, GetAutoDiscoveredAliases(key))); return true; } From 81c1b1808c4181eef00f868e44992ba5962f60ce Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 16 Oct 2022 20:40:41 +0000 Subject: [PATCH 07/22] Generate async files --- .../Async/Cache/StandardQueryCache.cs | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/NHibernate/Async/Cache/StandardQueryCache.cs b/src/NHibernate/Async/Cache/StandardQueryCache.cs index 971a4332a89..a44d551f153 100644 --- a/src/NHibernate/Async/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Async/Cache/StandardQueryCache.cs @@ -37,24 +37,21 @@ public Task ClearAsync(CancellationToken cancellationToken) } /// - public async Task PutAsync( + public Task PutAsync( QueryKey key, QueryParameters queryParameters, ICacheAssembler[] returnTypes, IList result, ISessionImplementor session, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); - if (queryParameters.NaturalKeyLookup && result.Count == 0) - return false; - - var ts = session.Factory.Settings.CacheProvider.NextTimestamp(); - - Log.Debug("caching query results in region: '{0}'; {1}", _regionName, key); - - await (Cache.PutAsync(key, await (GetCacheableResultAsync(returnTypes, session, result, ts, GetAutoDiscoveredAliases(key), cancellationToken)).ConfigureAwait(false), cancellationToken)).ConfigureAwait(false); - - return true; + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + // 6.0 TODO: inline the call. +#pragma warning disable 612 + return PutAsync(key, returnTypes, result, queryParameters.NaturalKeyLookup, session, cancellationToken); +#pragma warning restore 612 } // Since 5.2 @@ -69,7 +66,7 @@ public async Task PutAsync(QueryKey key, ICacheAssembler[] returnTypes, IL Log.Debug("caching query results in region: '{0}'; {1}", _regionName, key); - await (Cache.PutAsync(key, await (GetCacheableResultAsync(returnTypes, session, result, ts, null, cancellationToken)).ConfigureAwait(false), cancellationToken)).ConfigureAwait(false); + await (Cache.PutAsync(key, await (GetCacheableResultAsync(returnTypes, session, result, ts, GetAutoDiscoveredAliases(key), cancellationToken)).ConfigureAwait(false), cancellationToken)).ConfigureAwait(false); return true; } From b38af027339b925322b61e7b2e83f1b7066e9657 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <12201973+fredericdelaporte@users.noreply.github.com> Date: Mon, 17 Oct 2022 00:03:42 +0200 Subject: [PATCH 08/22] Fix whitespaces --- src/NHibernate/Loader/Loader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NHibernate/Loader/Loader.cs b/src/NHibernate/Loader/Loader.cs index bfb00ddf036..6a74b42032b 100644 --- a/src/NHibernate/Loader/Loader.cs +++ b/src/NHibernate/Loader/Loader.cs @@ -1843,7 +1843,7 @@ internal virtual bool IsCacheable(QueryParameters queryParameters) internal bool IsCacheable(QueryParameters queryParameters, bool supportsQueryCache, IEnumerable persisters) { bool isCacheable = Factory.Settings.IsQueryCacheEnabled && queryParameters.Cacheable; - if (isCacheable && !supportsQueryCache) + if (isCacheable && !supportsQueryCache) { if (Factory.Settings.QueryThrowNeverCached) { From b7bea83120deb287fda75b8e6ca03002946c70ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <12201973+fredericdelaporte@users.noreply.github.com> Date: Mon, 17 Oct 2022 00:14:32 +0200 Subject: [PATCH 09/22] Fix some tests --- src/NHibernate.Test/QueryTest/MultiCriteriaFixture.cs | 3 ++- src/NHibernate.Test/QueryTest/MultipleMixedQueriesFixture.cs | 3 ++- src/NHibernate.Test/QueryTest/MultipleQueriesFixture.cs | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/NHibernate.Test/QueryTest/MultiCriteriaFixture.cs b/src/NHibernate.Test/QueryTest/MultiCriteriaFixture.cs index 2b170b95905..76a415e08ac 100644 --- a/src/NHibernate.Test/QueryTest/MultiCriteriaFixture.cs +++ b/src/NHibernate.Test/QueryTest/MultiCriteriaFixture.cs @@ -141,7 +141,8 @@ public void CanGetMultiQueryFromSecondLevelCache() var cacheHashtable = MultipleQueriesFixture.GetHashTableUsedAsQueryCache(Sfi); var cachedListEntry = (IList)new ArrayList(cacheHashtable.Values)[0]; - var cachedQuery = (IList)cachedListEntry[1]; + // The first element is a timestamp, the second is aliases, then only we have the cached data. + var cachedQuery = (IList)cachedListEntry[2] ?? throw new InvalidOperationException("Cached data is null"); var firstQueryResults = (IList)cachedQuery[0]; firstQueryResults.Clear(); diff --git a/src/NHibernate.Test/QueryTest/MultipleMixedQueriesFixture.cs b/src/NHibernate.Test/QueryTest/MultipleMixedQueriesFixture.cs index 561d679a0a7..6c4413b395d 100644 --- a/src/NHibernate.Test/QueryTest/MultipleMixedQueriesFixture.cs +++ b/src/NHibernate.Test/QueryTest/MultipleMixedQueriesFixture.cs @@ -71,7 +71,8 @@ public void CanGetMultiQueryFromSecondLevelCache() var cacheHashtable = MultipleQueriesFixture.GetHashTableUsedAsQueryCache(Sfi); var cachedListEntry = (IList)new ArrayList(cacheHashtable.Values)[0]; - var cachedQuery = (IList)cachedListEntry[1]; + // The first element is a timestamp, the second is aliases, then only we have the cached data. + var cachedQuery = (IList) cachedListEntry[2] ?? throw new InvalidOperationException("Cached data is null"); var firstQueryResults = (IList)cachedQuery[0]; firstQueryResults.Clear(); diff --git a/src/NHibernate.Test/QueryTest/MultipleQueriesFixture.cs b/src/NHibernate.Test/QueryTest/MultipleQueriesFixture.cs index 6c4bd1063b6..c4cf5a6a64b 100644 --- a/src/NHibernate.Test/QueryTest/MultipleQueriesFixture.cs +++ b/src/NHibernate.Test/QueryTest/MultipleQueriesFixture.cs @@ -74,7 +74,8 @@ public void CanGetMultiQueryFromSecondLevelCache() var cacheHashtable = GetHashTableUsedAsQueryCache(Sfi); var cachedListEntry = (IList)new ArrayList(cacheHashtable.Values)[0]; - var cachedQuery = (IList)cachedListEntry[1]; + // The first element is a timestamp, the second is aliases, then only we have the cached data. + var cachedQuery = (IList) cachedListEntry[2] ?? throw new InvalidOperationException("Cached data is null"); var firstQueryResults = (IList)cachedQuery[0]; firstQueryResults.Clear(); From 300cceeb93f9bbb35661b087503d9b5027e2abaf Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 16 Oct 2022 22:17:38 +0000 Subject: [PATCH 10/22] Generate async files --- src/NHibernate.Test/Async/QueryTest/MultiCriteriaFixture.cs | 3 ++- .../Async/QueryTest/MultipleMixedQueriesFixture.cs | 3 ++- src/NHibernate.Test/Async/QueryTest/MultipleQueriesFixture.cs | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/NHibernate.Test/Async/QueryTest/MultiCriteriaFixture.cs b/src/NHibernate.Test/Async/QueryTest/MultiCriteriaFixture.cs index ff81250d911..2e2aabb7e1b 100644 --- a/src/NHibernate.Test/Async/QueryTest/MultiCriteriaFixture.cs +++ b/src/NHibernate.Test/Async/QueryTest/MultiCriteriaFixture.cs @@ -153,7 +153,8 @@ public async Task CanGetMultiQueryFromSecondLevelCacheAsync() var cacheHashtable = MultipleQueriesFixtureAsync.GetHashTableUsedAsQueryCache(Sfi); var cachedListEntry = (IList)new ArrayList(cacheHashtable.Values)[0]; - var cachedQuery = (IList)cachedListEntry[1]; + // The first element is a timestamp, the second is aliases, then only we have the cached data. + var cachedQuery = (IList)cachedListEntry[2] ?? throw new InvalidOperationException("Cached data is null"); var firstQueryResults = (IList)cachedQuery[0]; firstQueryResults.Clear(); diff --git a/src/NHibernate.Test/Async/QueryTest/MultipleMixedQueriesFixture.cs b/src/NHibernate.Test/Async/QueryTest/MultipleMixedQueriesFixture.cs index 5fc7d5f721a..00e5f08e167 100644 --- a/src/NHibernate.Test/Async/QueryTest/MultipleMixedQueriesFixture.cs +++ b/src/NHibernate.Test/Async/QueryTest/MultipleMixedQueriesFixture.cs @@ -83,7 +83,8 @@ public async Task CanGetMultiQueryFromSecondLevelCacheAsync() var cacheHashtable = MultipleQueriesFixtureAsync.GetHashTableUsedAsQueryCache(Sfi); var cachedListEntry = (IList)new ArrayList(cacheHashtable.Values)[0]; - var cachedQuery = (IList)cachedListEntry[1]; + // The first element is a timestamp, the second is aliases, then only we have the cached data. + var cachedQuery = (IList) cachedListEntry[2] ?? throw new InvalidOperationException("Cached data is null"); var firstQueryResults = (IList)cachedQuery[0]; firstQueryResults.Clear(); diff --git a/src/NHibernate.Test/Async/QueryTest/MultipleQueriesFixture.cs b/src/NHibernate.Test/Async/QueryTest/MultipleQueriesFixture.cs index 688bce5dc72..ef8f90793a4 100644 --- a/src/NHibernate.Test/Async/QueryTest/MultipleQueriesFixture.cs +++ b/src/NHibernate.Test/Async/QueryTest/MultipleQueriesFixture.cs @@ -86,7 +86,8 @@ public async Task CanGetMultiQueryFromSecondLevelCacheAsync() var cacheHashtable = GetHashTableUsedAsQueryCache(Sfi); var cachedListEntry = (IList)new ArrayList(cacheHashtable.Values)[0]; - var cachedQuery = (IList)cachedListEntry[1]; + // The first element is a timestamp, the second is aliases, then only we have the cached data. + var cachedQuery = (IList) cachedListEntry[2] ?? throw new InvalidOperationException("Cached data is null"); var firstQueryResults = (IList)cachedQuery[0]; firstQueryResults.Clear(); From c355c3451987624ef7a7f76d15f855605e24b00b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <12201973+fredericDelaporte@users.noreply.github.com> Date: Tue, 18 Oct 2022 21:13:06 +0200 Subject: [PATCH 11/22] Simplify code Co-authored-by: Roman Artiukhin --- src/NHibernate/Cache/StandardQueryCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NHibernate/Cache/StandardQueryCache.cs b/src/NHibernate/Cache/StandardQueryCache.cs index 756a55f1436..23e94eb3ca7 100644 --- a/src/NHibernate/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Cache/StandardQueryCache.cs @@ -222,7 +222,7 @@ public bool[] PutMany( private static string[] GetAutoDiscoveredAliases(QueryKey key) { - return key.ResultTransformer?.AutoDiscoverTypes == true ? key.ResultTransformer.AutoDiscoveredAliases : null; + return key.ResultTransformer?.AutoDiscoveredAliases; } /// From ec0c5a0e10ea088e99e2886e52a4a77d589c660d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <12201973+fredericdelaporte@users.noreply.github.com> Date: Sun, 23 Oct 2022 13:25:51 +0200 Subject: [PATCH 12/22] Implement breaking change minimizing suggestions --- .../Async/QueryTest/MultiCriteriaFixture.cs | 4 +- .../QueryTest/MultipleMixedQueriesFixture.cs | 4 +- .../Async/QueryTest/MultipleQueriesFixture.cs | 4 +- .../QueryTest/MultiCriteriaFixture.cs | 4 +- .../QueryTest/MultipleMixedQueriesFixture.cs | 4 +- .../QueryTest/MultipleQueriesFixture.cs | 4 +- src/NHibernate/Cache/StandardQueryCache.cs | 61 +++++++++++++------ 7 files changed, 53 insertions(+), 32 deletions(-) diff --git a/src/NHibernate.Test/Async/QueryTest/MultiCriteriaFixture.cs b/src/NHibernate.Test/Async/QueryTest/MultiCriteriaFixture.cs index 2e2aabb7e1b..77feb83fd0e 100644 --- a/src/NHibernate.Test/Async/QueryTest/MultiCriteriaFixture.cs +++ b/src/NHibernate.Test/Async/QueryTest/MultiCriteriaFixture.cs @@ -153,8 +153,8 @@ public async Task CanGetMultiQueryFromSecondLevelCacheAsync() var cacheHashtable = MultipleQueriesFixtureAsync.GetHashTableUsedAsQueryCache(Sfi); var cachedListEntry = (IList)new ArrayList(cacheHashtable.Values)[0]; - // The first element is a timestamp, the second is aliases, then only we have the cached data. - var cachedQuery = (IList)cachedListEntry[2] ?? throw new InvalidOperationException("Cached data is null"); + // The first element is a timestamp, then only we have the cached data. + var cachedQuery = (IList) cachedListEntry[1] ?? throw new InvalidOperationException("Cached data is null"); var firstQueryResults = (IList)cachedQuery[0]; firstQueryResults.Clear(); diff --git a/src/NHibernate.Test/Async/QueryTest/MultipleMixedQueriesFixture.cs b/src/NHibernate.Test/Async/QueryTest/MultipleMixedQueriesFixture.cs index 00e5f08e167..b04818aed4c 100644 --- a/src/NHibernate.Test/Async/QueryTest/MultipleMixedQueriesFixture.cs +++ b/src/NHibernate.Test/Async/QueryTest/MultipleMixedQueriesFixture.cs @@ -83,8 +83,8 @@ public async Task CanGetMultiQueryFromSecondLevelCacheAsync() var cacheHashtable = MultipleQueriesFixtureAsync.GetHashTableUsedAsQueryCache(Sfi); var cachedListEntry = (IList)new ArrayList(cacheHashtable.Values)[0]; - // The first element is a timestamp, the second is aliases, then only we have the cached data. - var cachedQuery = (IList) cachedListEntry[2] ?? throw new InvalidOperationException("Cached data is null"); + // The first element is a timestamp, then only we have the cached data. + var cachedQuery = (IList) cachedListEntry[1] ?? throw new InvalidOperationException("Cached data is null"); var firstQueryResults = (IList)cachedQuery[0]; firstQueryResults.Clear(); diff --git a/src/NHibernate.Test/Async/QueryTest/MultipleQueriesFixture.cs b/src/NHibernate.Test/Async/QueryTest/MultipleQueriesFixture.cs index ef8f90793a4..118d9101088 100644 --- a/src/NHibernate.Test/Async/QueryTest/MultipleQueriesFixture.cs +++ b/src/NHibernate.Test/Async/QueryTest/MultipleQueriesFixture.cs @@ -86,8 +86,8 @@ public async Task CanGetMultiQueryFromSecondLevelCacheAsync() var cacheHashtable = GetHashTableUsedAsQueryCache(Sfi); var cachedListEntry = (IList)new ArrayList(cacheHashtable.Values)[0]; - // The first element is a timestamp, the second is aliases, then only we have the cached data. - var cachedQuery = (IList) cachedListEntry[2] ?? throw new InvalidOperationException("Cached data is null"); + // The first element is a timestamp, then only we have the cached data. + var cachedQuery = (IList) cachedListEntry[1] ?? throw new InvalidOperationException("Cached data is null"); var firstQueryResults = (IList)cachedQuery[0]; firstQueryResults.Clear(); diff --git a/src/NHibernate.Test/QueryTest/MultiCriteriaFixture.cs b/src/NHibernate.Test/QueryTest/MultiCriteriaFixture.cs index 76a415e08ac..c5aa9fd6672 100644 --- a/src/NHibernate.Test/QueryTest/MultiCriteriaFixture.cs +++ b/src/NHibernate.Test/QueryTest/MultiCriteriaFixture.cs @@ -141,8 +141,8 @@ public void CanGetMultiQueryFromSecondLevelCache() var cacheHashtable = MultipleQueriesFixture.GetHashTableUsedAsQueryCache(Sfi); var cachedListEntry = (IList)new ArrayList(cacheHashtable.Values)[0]; - // The first element is a timestamp, the second is aliases, then only we have the cached data. - var cachedQuery = (IList)cachedListEntry[2] ?? throw new InvalidOperationException("Cached data is null"); + // The first element is a timestamp, then only we have the cached data. + var cachedQuery = (IList)cachedListEntry[1] ?? throw new InvalidOperationException("Cached data is null"); var firstQueryResults = (IList)cachedQuery[0]; firstQueryResults.Clear(); diff --git a/src/NHibernate.Test/QueryTest/MultipleMixedQueriesFixture.cs b/src/NHibernate.Test/QueryTest/MultipleMixedQueriesFixture.cs index 6c4413b395d..80fa0fc472f 100644 --- a/src/NHibernate.Test/QueryTest/MultipleMixedQueriesFixture.cs +++ b/src/NHibernate.Test/QueryTest/MultipleMixedQueriesFixture.cs @@ -71,8 +71,8 @@ public void CanGetMultiQueryFromSecondLevelCache() var cacheHashtable = MultipleQueriesFixture.GetHashTableUsedAsQueryCache(Sfi); var cachedListEntry = (IList)new ArrayList(cacheHashtable.Values)[0]; - // The first element is a timestamp, the second is aliases, then only we have the cached data. - var cachedQuery = (IList) cachedListEntry[2] ?? throw new InvalidOperationException("Cached data is null"); + // The first element is a timestamp, then only we have the cached data. + var cachedQuery = (IList) cachedListEntry[1] ?? throw new InvalidOperationException("Cached data is null"); var firstQueryResults = (IList)cachedQuery[0]; firstQueryResults.Clear(); diff --git a/src/NHibernate.Test/QueryTest/MultipleQueriesFixture.cs b/src/NHibernate.Test/QueryTest/MultipleQueriesFixture.cs index c4cf5a6a64b..8249ea90002 100644 --- a/src/NHibernate.Test/QueryTest/MultipleQueriesFixture.cs +++ b/src/NHibernate.Test/QueryTest/MultipleQueriesFixture.cs @@ -74,8 +74,8 @@ public void CanGetMultiQueryFromSecondLevelCache() var cacheHashtable = GetHashTableUsedAsQueryCache(Sfi); var cachedListEntry = (IList)new ArrayList(cacheHashtable.Values)[0]; - // The first element is a timestamp, the second is aliases, then only we have the cached data. - var cachedQuery = (IList) cachedListEntry[2] ?? throw new InvalidOperationException("Cached data is null"); + // The first element is a timestamp, then only we have the cached data. + var cachedQuery = (IList) cachedListEntry[1] ?? throw new InvalidOperationException("Cached data is null"); var firstQueryResults = (IList)cachedQuery[0]; firstQueryResults.Clear(); diff --git a/src/NHibernate/Cache/StandardQueryCache.cs b/src/NHibernate/Cache/StandardQueryCache.cs index 23e94eb3ca7..da61205440e 100644 --- a/src/NHibernate/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Cache/StandardQueryCache.cs @@ -1,7 +1,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Linq; using NHibernate.Cfg; using NHibernate.Engine; using NHibernate.Persister.Collection; @@ -18,7 +17,7 @@ namespace NHibernate.Cache /// public partial class StandardQueryCache : IQueryCache, IBatchableQueryCache { - private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof (StandardQueryCache)); + private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(StandardQueryCache)); private readonly string _regionName; private readonly UpdateTimestampsCache _updateTimestampsCache; private readonly CacheBase _cache; @@ -133,7 +132,7 @@ public IList Get( return null; } - var timestamp = (long) cacheable[0]; + var timestamp = GetResultsMetadata(cacheable, out var aliases); if (Log.IsDebugEnabled()) Log.Debug("Checking query spaces for up-to-dateness [{0}]", StringHelper.CollectionToString(spaces)); @@ -148,7 +147,6 @@ public IList Get( if (result != null && key.ResultTransformer?.AutoDiscoverTypes == true && result.Count > 0) { - var aliases = (string[]) cacheable[1]; key.ResultTransformer.SupplyAutoDiscoveredParameters(queryParameters.ResultTransformer, aliases); } @@ -174,7 +172,7 @@ public IList Get(QueryKey key, ICacheAssembler[] returnTypes, bool isNaturalKeyL return null; } - var timestamp = (long) cacheable[0]; + var timestamp = GetResultsMetadata(cacheable, out var _); if (Log.IsDebugEnabled()) Log.Debug("Checking query spaces for up-to-dateness [{0}]", StringHelper.CollectionToString(spaces)); @@ -256,10 +254,16 @@ public IList[] GetMany( spacesToCheck.Add(querySpaces); checkedSpacesIndexes.Add(i); - // The timestamp is the first element of the cache result. - checkedSpacesTimestamp.Add((long) cacheable[0]); + var timestamp = GetResultsMetadata(cacheable, out var aliases); + checkedSpacesTimestamp.Add(timestamp); if (Log.IsDebugEnabled()) Log.Debug("Checking query spaces for up-to-dateness [{0}]", StringHelper.CollectionToString(querySpaces)); + + var key = keys[i]; + if (key.ResultTransformer?.AutoDiscoverTypes == true && HasResults(cacheable)) + { + key.ResultTransformer.SupplyAutoDiscoveredParameters(queryParameters[i].ResultTransformer, aliases); + } } var upToDates = spacesToCheck.Count > 0 @@ -298,12 +302,6 @@ public IList[] GetMany( finalReturnTypes[i] = GetReturnTypes(key, returnTypes[i], cacheable); PerformBeforeAssemble(finalReturnTypes[i], session, cacheable); - - if (key.ResultTransformer?.AutoDiscoverTypes == true && cacheable.Count > 2) - { - var aliases = (string[]) cacheable[1]; - key.ResultTransformer.SupplyAutoDiscoveredParameters(queryParams.ResultTransformer, aliases); - } } for (var i = 0; i < keys.Length; i++) @@ -374,7 +372,12 @@ private static List GetCacheableResult( IList result, long ts, string[] aliases) { - var cacheable = new List(result.Count + 2) { ts, aliases }; + var cacheable = + new List(result.Count + 1) + { + aliases == null ? ts : new object[] { ts, aliases } + }; + foreach (var row in result) { if (returnTypes.Length == 1) @@ -383,19 +386,37 @@ private static List GetCacheableResult( } else { - cacheable.Add(TypeHelper.Disassemble((object[])row, returnTypes, null, session, null)); + cacheable.Add(TypeHelper.Disassemble((object[]) row, returnTypes, null, session, null)); } } return cacheable; } - private static IEnumerable GetResultsEnumerable(IList results) + private static long GetResultsMetadata(IList cacheable, out string[] aliases) + { + aliases = null; + + var metadata = cacheable[0]; + var timestamp = metadata as long?; + if (timestamp.HasValue) + return timestamp.Value; + + var metadataArray = (object[]) metadata; + aliases = (string[]) metadataArray[1]; + return (long) metadataArray[0]; + } + + private static bool HasResults(IList cacheable) + // First element is the timestamp. + => cacheable.Count > 1; + + private static IEnumerable GetResultsEnumerable(IList cacheable) { - // Skip first element, it is the timestamp, and the second, it is the aliases. - for (var i = 2; i < results.Count; i++) + // Skip first element, it is the timestamp. + for (var i = 1; i < cacheable.Count; i++) { - yield return results[i]; + yield return cacheable[i]; } } @@ -404,7 +425,7 @@ private static ICacheAssembler[] GetReturnTypes( ICacheAssembler[] returnTypes, IList cacheable) { - if (key.ResultTransformer?.AutoDiscoverTypes == true && cacheable.Count > 2) + if (key.ResultTransformer?.AutoDiscoverTypes == true && HasResults(cacheable)) { returnTypes = GuessTypes(cacheable); } From fda5a751b8a212b08d413b412efbbc32723ce2ba Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 23 Oct 2022 11:30:49 +0000 Subject: [PATCH 13/22] Generate async files --- .../Async/QueryTest/MultiCriteriaFixture.cs | 2 +- .../Async/Cache/StandardQueryCache.cs | 31 ++++++++++--------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/NHibernate.Test/Async/QueryTest/MultiCriteriaFixture.cs b/src/NHibernate.Test/Async/QueryTest/MultiCriteriaFixture.cs index 77feb83fd0e..6479b9081c2 100644 --- a/src/NHibernate.Test/Async/QueryTest/MultiCriteriaFixture.cs +++ b/src/NHibernate.Test/Async/QueryTest/MultiCriteriaFixture.cs @@ -154,7 +154,7 @@ public async Task CanGetMultiQueryFromSecondLevelCacheAsync() var cacheHashtable = MultipleQueriesFixtureAsync.GetHashTableUsedAsQueryCache(Sfi); var cachedListEntry = (IList)new ArrayList(cacheHashtable.Values)[0]; // The first element is a timestamp, then only we have the cached data. - var cachedQuery = (IList) cachedListEntry[1] ?? throw new InvalidOperationException("Cached data is null"); + var cachedQuery = (IList)cachedListEntry[1] ?? throw new InvalidOperationException("Cached data is null"); var firstQueryResults = (IList)cachedQuery[0]; firstQueryResults.Clear(); diff --git a/src/NHibernate/Async/Cache/StandardQueryCache.cs b/src/NHibernate/Async/Cache/StandardQueryCache.cs index a44d551f153..9739e969bbd 100644 --- a/src/NHibernate/Async/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Async/Cache/StandardQueryCache.cs @@ -11,7 +11,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Linq; using NHibernate.Cfg; using NHibernate.Engine; using NHibernate.Persister.Collection; @@ -100,7 +99,7 @@ public async Task GetAsync( return null; } - var timestamp = (long) cacheable[0]; + var timestamp = GetResultsMetadata(cacheable, out var aliases); if (Log.IsDebugEnabled()) Log.Debug("Checking query spaces for up-to-dateness [{0}]", StringHelper.CollectionToString(spaces)); @@ -115,7 +114,6 @@ public async Task GetAsync( if (result != null && key.ResultTransformer?.AutoDiscoverTypes == true && result.Count > 0) { - var aliases = (string[]) cacheable[1]; key.ResultTransformer.SupplyAutoDiscoveredParameters(queryParameters.ResultTransformer, aliases); } @@ -142,7 +140,7 @@ public async Task GetAsync(QueryKey key, ICacheAssembler[] returnTypes, b return null; } - var timestamp = (long) cacheable[0]; + var timestamp = GetResultsMetadata(cacheable, out var _); if (Log.IsDebugEnabled()) Log.Debug("Checking query spaces for up-to-dateness [{0}]", StringHelper.CollectionToString(spaces)); @@ -221,10 +219,16 @@ public async Task GetManyAsync( spacesToCheck.Add(querySpaces); checkedSpacesIndexes.Add(i); - // The timestamp is the first element of the cache result. - checkedSpacesTimestamp.Add((long) cacheable[0]); + var timestamp = GetResultsMetadata(cacheable, out var aliases); + checkedSpacesTimestamp.Add(timestamp); if (Log.IsDebugEnabled()) Log.Debug("Checking query spaces for up-to-dateness [{0}]", StringHelper.CollectionToString(querySpaces)); + + var key = keys[i]; + if (key.ResultTransformer?.AutoDiscoverTypes == true && HasResults(cacheable)) + { + key.ResultTransformer.SupplyAutoDiscoveredParameters(queryParameters[i].ResultTransformer, aliases); + } } var upToDates = spacesToCheck.Count > 0 @@ -263,12 +267,6 @@ public async Task GetManyAsync( finalReturnTypes[i] = GetReturnTypes(key, returnTypes[i], cacheable); await (PerformBeforeAssembleAsync(finalReturnTypes[i], session, cacheable, cancellationToken)).ConfigureAwait(false); - - if (key.ResultTransformer?.AutoDiscoverTypes == true && cacheable.Count > 2) - { - var aliases = (string[]) cacheable[1]; - key.ResultTransformer.SupplyAutoDiscoveredParameters(queryParams.ResultTransformer, aliases); - } } for (var i = 0; i < keys.Length; i++) @@ -334,7 +332,12 @@ private static async Task> GetCacheableResultAsync( long ts, string[] aliases, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - var cacheable = new List(result.Count + 2) { ts, aliases }; + var cacheable = + new List(result.Count + 1) + { + aliases == null ? ts : new object[] { ts, aliases } + }; + foreach (var row in result) { if (returnTypes.Length == 1) @@ -343,7 +346,7 @@ private static async Task> GetCacheableResultAsync( } else { - cacheable.Add(await (TypeHelper.DisassembleAsync((object[])row, returnTypes, null, session, null, cancellationToken)).ConfigureAwait(false)); + cacheable.Add(await (TypeHelper.DisassembleAsync((object[]) row, returnTypes, null, session, null, cancellationToken)).ConfigureAwait(false)); } } From ee43157892b717223e33632aca8ab41d9bcbe46e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <12201973+fredericdelaporte@users.noreply.github.com> Date: Sun, 23 Oct 2022 13:34:51 +0200 Subject: [PATCH 14/22] Fix some more metadata reserved slots changes --- src/NHibernate/Cache/StandardQueryCache.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/NHibernate/Cache/StandardQueryCache.cs b/src/NHibernate/Cache/StandardQueryCache.cs index da61205440e..877e99679cd 100644 --- a/src/NHibernate/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Cache/StandardQueryCache.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; using NHibernate.Cfg; using NHibernate.Engine; using NHibernate.Persister.Collection; @@ -465,7 +466,7 @@ private IList PerformAssemble( { try { - var result = new List(cacheable.Count - 2); + var result = new List(cacheable.Count - 1); if (returnTypes.Length == 1) { var returnType = returnTypes[0]; @@ -574,7 +575,7 @@ private IList GetResultFromCacheable( private static ICacheAssembler[] GuessTypes(IList cacheable) { - var colCount = (cacheable[2] as object[])?.Length ?? 1; + var colCount = (GetResultsEnumerable(cacheable).First() as object[])?.Length ?? 1; var returnTypes = new ICacheAssembler[colCount]; if (colCount == 1) { From 594d6946e125526a52bb15e2933118ece20ea381 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 23 Oct 2022 11:37:24 +0000 Subject: [PATCH 15/22] Generate async files --- src/NHibernate/Async/Cache/StandardQueryCache.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/NHibernate/Async/Cache/StandardQueryCache.cs b/src/NHibernate/Async/Cache/StandardQueryCache.cs index 9739e969bbd..d21e9a075ca 100644 --- a/src/NHibernate/Async/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Async/Cache/StandardQueryCache.cs @@ -11,6 +11,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; using NHibernate.Cfg; using NHibernate.Engine; using NHibernate.Persister.Collection; @@ -387,7 +388,7 @@ private async Task PerformAssembleAsync( cancellationToken.ThrowIfCancellationRequested(); try { - var result = new List(cacheable.Count - 2); + var result = new List(cacheable.Count - 1); if (returnTypes.Length == 1) { var returnType = returnTypes[0]; From b0a2fd254f1ba1982f515c3ee43c3f412a62037c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <12201973+fredericdelaporte@users.noreply.github.com> Date: Sun, 23 Oct 2022 17:06:17 +0200 Subject: [PATCH 16/22] Fix a regression fromminimizing breaking change And fix the empty case. --- .../Async/QueryTest/MultiCriteriaFixture.cs | 28 +++---- .../QueryTest/MultipleMixedQueriesFixture.cs | 22 +++--- .../Async/QueryTest/MultipleQueriesFixture.cs | 22 +++--- .../SqlTest/Query/NativeSQLQueriesFixture.cs | 73 ++++++++++++++++++- .../QueryTest/MultiCriteriaFixture.cs | 28 +++---- .../QueryTest/MultipleMixedQueriesFixture.cs | 22 +++--- .../QueryTest/MultipleQueriesFixture.cs | 22 +++--- .../SqlTest/Query/NativeSQLQueriesFixture.cs | 73 ++++++++++++++++++- .../Async/Cache/StandardQueryCache.cs | 31 +++++--- src/NHibernate/Cache/StandardQueryCache.cs | 39 +++++++--- .../Transform/CacheableResultTransformer.cs | 12 ++- 11 files changed, 271 insertions(+), 101 deletions(-) diff --git a/src/NHibernate.Test/Async/QueryTest/MultiCriteriaFixture.cs b/src/NHibernate.Test/Async/QueryTest/MultiCriteriaFixture.cs index 6479b9081c2..848ead9056e 100644 --- a/src/NHibernate.Test/Async/QueryTest/MultiCriteriaFixture.cs +++ b/src/NHibernate.Test/Async/QueryTest/MultiCriteriaFixture.cs @@ -139,7 +139,7 @@ public async Task CanUseSecondLevelCacheWithPositionalParametersAsync() await (CreateItemsAsync()); - await (DoMutiQueryAndAssertAsync()); + await (DoMultiQueryAndAssertAsync()); Assert.AreEqual(1, cacheHashtable.Count); } @@ -148,20 +148,20 @@ public async Task CanUseSecondLevelCacheWithPositionalParametersAsync() public async Task CanGetMultiQueryFromSecondLevelCacheAsync() { await (CreateItemsAsync()); - //set the query in the cache - await (DoMutiQueryAndAssertAsync()); + // Set the query in the cache. + await (DoMultiQueryAndAssertAsync()); var cacheHashtable = MultipleQueriesFixtureAsync.GetHashTableUsedAsQueryCache(Sfi); - var cachedListEntry = (IList)new ArrayList(cacheHashtable.Values)[0]; + var cachedListEntry = (IList) new ArrayList(cacheHashtable.Values)[0]; // The first element is a timestamp, then only we have the cached data. - var cachedQuery = (IList)cachedListEntry[1] ?? throw new InvalidOperationException("Cached data is null"); + var cachedQuery = (IList) cachedListEntry[1] ?? throw new InvalidOperationException("Cached data is null"); - var firstQueryResults = (IList)cachedQuery[0]; + var firstQueryResults = (IList) cachedQuery[0]; firstQueryResults.Clear(); firstQueryResults.Add(3); firstQueryResults.Add(4); - var secondQueryResults = (IList)cachedQuery[1]; + var secondQueryResults = (IList) cachedQuery[1]; secondQueryResults[0] = 2; using (var s = Sfi.OpenSession()) @@ -173,9 +173,9 @@ public async Task CanGetMultiQueryFromSecondLevelCacheAsync() .Add(CriteriaTransformer.Clone(criteria).SetProjection(Projections.RowCount())); multiCriteria.SetCacheable(true); var results = await (multiCriteria.ListAsync()); - var items = (IList)results[0]; + var items = (IList) results[0]; Assert.AreEqual(2, items.Count); - var count = (int)((IList)results[1])[0]; + var count = (int) ((IList) results[1])[0]; Assert.AreEqual(2L, count); } } @@ -185,12 +185,12 @@ public async Task CanUpdateStatisticsWhenGetMultiQueryFromSecondLevelCacheAsync( { await (CreateItemsAsync()); - await (DoMutiQueryAndAssertAsync()); + await (DoMultiQueryAndAssertAsync()); Assert.AreEqual(0, Sfi.Statistics.QueryCacheHitCount); Assert.AreEqual(1, Sfi.Statistics.QueryCacheMissCount); Assert.AreEqual(1, Sfi.Statistics.QueryCachePutCount); - await (DoMutiQueryAndAssertAsync()); + await (DoMultiQueryAndAssertAsync()); Assert.AreEqual(1, Sfi.Statistics.QueryCacheHitCount); Assert.AreEqual(1, Sfi.Statistics.QueryCacheMissCount); Assert.AreEqual(1, Sfi.Statistics.QueryCachePutCount); @@ -391,7 +391,7 @@ public async Task CanNotRetrieveDetachedCriteriaResultWithUnknownKeyAsync() } } - private async Task DoMutiQueryAndAssertAsync(CancellationToken cancellationToken = default(CancellationToken)) + private async Task DoMultiQueryAndAssertAsync(CancellationToken cancellationToken = default(CancellationToken)) { using (var s = OpenSession()) { @@ -402,9 +402,9 @@ public async Task CanNotRetrieveDetachedCriteriaResultWithUnknownKeyAsync() .Add(CriteriaTransformer.Clone(criteria).SetProjection(Projections.RowCount())); multiCriteria.SetCacheable(true); var results = await (multiCriteria.ListAsync(cancellationToken)); - var items = (IList)results[0]; + var items = (IList) results[0]; Assert.AreEqual(89, items.Count); - var count = (int)((IList)results[1])[0]; + var count = (int) ((IList) results[1])[0]; Assert.AreEqual(99L, count); } } diff --git a/src/NHibernate.Test/Async/QueryTest/MultipleMixedQueriesFixture.cs b/src/NHibernate.Test/Async/QueryTest/MultipleMixedQueriesFixture.cs index b04818aed4c..9e4b39d77cf 100644 --- a/src/NHibernate.Test/Async/QueryTest/MultipleMixedQueriesFixture.cs +++ b/src/NHibernate.Test/Async/QueryTest/MultipleMixedQueriesFixture.cs @@ -78,20 +78,20 @@ public void NH_1085_WillGiveReasonableErrorIfBadParameterNameAsync() public async Task CanGetMultiQueryFromSecondLevelCacheAsync() { await (CreateItemsAsync()); - //set the query in the cache - await (DoMutiQueryAndAssertAsync()); + // Set the query in the cache. + await (DoMultiQueryAndAssertAsync()); var cacheHashtable = MultipleQueriesFixtureAsync.GetHashTableUsedAsQueryCache(Sfi); - var cachedListEntry = (IList)new ArrayList(cacheHashtable.Values)[0]; + var cachedListEntry = (IList) new ArrayList(cacheHashtable.Values)[0]; // The first element is a timestamp, then only we have the cached data. var cachedQuery = (IList) cachedListEntry[1] ?? throw new InvalidOperationException("Cached data is null"); - var firstQueryResults = (IList)cachedQuery[0]; + var firstQueryResults = (IList) cachedQuery[0]; firstQueryResults.Clear(); firstQueryResults.Add(3); firstQueryResults.Add(4); - var secondQueryResults = (IList)cachedQuery[1]; + var secondQueryResults = (IList) cachedQuery[1]; secondQueryResults[0] = 2L; using (var s = Sfi.OpenSession()) @@ -104,9 +104,9 @@ public async Task CanGetMultiQueryFromSecondLevelCacheAsync() .SetInt32(0, 50)); multiQuery.SetCacheable(true); var results = await (multiQuery.ListAsync()); - var items = (IList)results[0]; + var items = (IList) results[0]; Assert.AreEqual(2, items.Count); - var count = (long)((IList)results[1])[0]; + var count = (long) ((IList) results[1])[0]; Assert.AreEqual(2L, count); } } @@ -186,12 +186,12 @@ public async Task CanUseSecondLevelCacheWithPositionalParametersAsync() await (CreateItemsAsync()); - await (DoMutiQueryAndAssertAsync()); + await (DoMultiQueryAndAssertAsync()); Assert.AreEqual(1, cacheHashtable.Count); } - private async Task DoMutiQueryAndAssertAsync(CancellationToken cancellationToken = default(CancellationToken)) + private async Task DoMultiQueryAndAssertAsync(CancellationToken cancellationToken = default(CancellationToken)) { using (var s = OpenSession()) { @@ -203,9 +203,9 @@ public async Task CanUseSecondLevelCacheWithPositionalParametersAsync() .SetInt32(0, 50)); multiQuery.SetCacheable(true); var results = await (multiQuery.ListAsync(cancellationToken)); - var items = (IList)results[0]; + var items = (IList) results[0]; Assert.AreEqual(89, items.Count); - var count = (long)((IList)results[1])[0]; + var count = (long) ((IList) results[1])[0]; Assert.AreEqual(99L, count); } } diff --git a/src/NHibernate.Test/Async/QueryTest/MultipleQueriesFixture.cs b/src/NHibernate.Test/Async/QueryTest/MultipleQueriesFixture.cs index 118d9101088..e8a84ffbe1a 100644 --- a/src/NHibernate.Test/Async/QueryTest/MultipleQueriesFixture.cs +++ b/src/NHibernate.Test/Async/QueryTest/MultipleQueriesFixture.cs @@ -81,20 +81,20 @@ public void NH_1085_WillGiveReasonableErrorIfBadParameterNameAsync() public async Task CanGetMultiQueryFromSecondLevelCacheAsync() { await (CreateItemsAsync()); - //set the query in the cache - await (DoMutiQueryAndAssertAsync()); + // Set the query in the cache. + await (DoMultiQueryAndAssertAsync()); var cacheHashtable = GetHashTableUsedAsQueryCache(Sfi); - var cachedListEntry = (IList)new ArrayList(cacheHashtable.Values)[0]; + var cachedListEntry = (IList) new ArrayList(cacheHashtable.Values)[0]; // The first element is a timestamp, then only we have the cached data. var cachedQuery = (IList) cachedListEntry[1] ?? throw new InvalidOperationException("Cached data is null"); - var firstQueryResults = (IList)cachedQuery[0]; + var firstQueryResults = (IList) cachedQuery[0]; firstQueryResults.Clear(); firstQueryResults.Add(3); firstQueryResults.Add(4); - var secondQueryResults = (IList)cachedQuery[1]; + var secondQueryResults = (IList) cachedQuery[1]; secondQueryResults[0] = 2L; using (var s = Sfi.OpenSession()) @@ -107,9 +107,9 @@ public async Task CanGetMultiQueryFromSecondLevelCacheAsync() .SetInt32(0, 50)); multiQuery.SetCacheable(true); var results = await (multiQuery.ListAsync()); - var items = (IList)results[0]; + var items = (IList) results[0]; Assert.AreEqual(2, items.Count); - var count = (long)((IList)results[1])[0]; + var count = (long) ((IList) results[1])[0]; Assert.AreEqual(2L, count); } } @@ -188,12 +188,12 @@ public async Task CanUseSecondLevelCacheWithPositionalParametersAsync() await (CreateItemsAsync()); - await (DoMutiQueryAndAssertAsync()); + await (DoMultiQueryAndAssertAsync()); Assert.AreEqual(1, cacheHashtable.Count); } - private async Task DoMutiQueryAndAssertAsync(CancellationToken cancellationToken = default(CancellationToken)) + private async Task DoMultiQueryAndAssertAsync(CancellationToken cancellationToken = default(CancellationToken)) { using (var s = OpenSession()) { @@ -205,9 +205,9 @@ public async Task CanUseSecondLevelCacheWithPositionalParametersAsync() .SetInt32(0, 50)); multiQuery.SetCacheable(true); var results = await (multiQuery.ListAsync(cancellationToken)); - var items = (IList)results[0]; + var items = (IList) results[0]; Assert.AreEqual(89, items.Count); - var count = (long)((IList)results[1])[0]; + var count = (long) ((IList) results[1])[0]; Assert.AreEqual(99L, count); } } diff --git a/src/NHibernate.Test/Async/SqlTest/Query/NativeSQLQueriesFixture.cs b/src/NHibernate.Test/Async/SqlTest/Query/NativeSQLQueriesFixture.cs index 2127d251b4d..e84422afc12 100644 --- a/src/NHibernate.Test/Async/SqlTest/Query/NativeSQLQueriesFixture.cs +++ b/src/NHibernate.Test/Async/SqlTest/Query/NativeSQLQueriesFixture.cs @@ -208,7 +208,7 @@ public async Task SQLQueryInterfaceCacheableAsync() } [Test(Description = "GH-2904")] - public async Task CacheableScalarSQLQueryAsync() + public async Task CacheableScalarSqlQueryAsync() { Organization ifa = new Organization("IFA"); Organization jboss = new Organization("JBoss"); @@ -270,7 +270,7 @@ class ResultDto } [Test(Description = "GH-3169")] - public async Task CacheableScalarSQLQueryWithTransformerAsync() + public async Task CacheableScalarSqlQueryWithTransformerAsync() { Organization ifa = new Organization("IFA"); @@ -306,7 +306,34 @@ async Task AssertQueryAsync(bool fromCache) } [Test(Description = "GH-3169")] - public async Task CacheableScalarSQLMultiQueryWithTransformerAsync() + public async Task CacheableScalarSqlEmptyQueryWithTransformerAsync() + { + async Task AssertQueryAsync(bool fromCache) + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + using (EnableStatisticsScope()) + { + var l = await (s.CreateSQLQuery("select org.NAME as regionCode from ORGANIZATION org") + .AddScalar("regionCode", NHibernateUtil.String) + .SetResultTransformer(Transformers.AliasToBean()) + .SetCacheable(true) + .ListAsync()); + await (t.CommitAsync()); + + Assert.That(l.Count, Is.EqualTo(0)); + var msg = "Results are expected from " + (fromCache ? "cache" : "DB"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(fromCache ? 0 : 1), msg); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(fromCache ? 1 : 0), msg); + } + } + + await (AssertQueryAsync(false)); + await (AssertQueryAsync(true)); + } + + [Test(Description = "GH-3169")] + public async Task CacheableScalarSqlMultiQueryWithTransformerAsync() { Organization ifa = new Organization("IFA"); @@ -354,6 +381,46 @@ async Task AssertQueryAsync(bool fromCache) await (AssertQueryAsync(true)); } + [Test(Description = "GH-3169")] + public async Task CacheableScalarSqlEmptyMultiQueryWithTransformerAsync() + { + async Task AssertQueryAsync(bool fromCache) + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + using (EnableStatisticsScope()) + { + var q1 = s.CreateSQLQuery("select org.NAME as regionCode from ORGANIZATION org") + .AddScalar("regionCode", NHibernateUtil.String) + .SetResultTransformer(Transformers.AliasToBean()) + .SetCacheable(true); + var q2 = s.CreateSQLQuery("select org.ORGID as orgId from ORGANIZATION org") + .AddScalar("orgId", NHibernateUtil.Int64) + .SetResultTransformer(Transformers.AliasToBean()) + .SetCacheable(true); + + var batch = s.CreateQueryBatch(); + batch.Add(q1); + batch.Add(q2); + await (batch.ExecuteAsync()); + + var l1 = await (batch.GetResultAsync(0)); + var l2 = await (batch.GetResultAsync(1)); + + await (t.CommitAsync()); + + Assert.That(l1.Count, Is.EqualTo(0), "Unexpected results count for the first query."); + Assert.That(l2.Count, Is.EqualTo(0), "Unexpected results count for the second query."); + var msg = "Results are expected from " + (fromCache ? "cache" : "DB"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(fromCache ? 0 : 2), msg); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(fromCache ? 2 : 0), msg); + } + } + + await (AssertQueryAsync(false)); + await (AssertQueryAsync(true)); + } + [Test] public async Task ResultSetMappingDefinitionAsync() { diff --git a/src/NHibernate.Test/QueryTest/MultiCriteriaFixture.cs b/src/NHibernate.Test/QueryTest/MultiCriteriaFixture.cs index c5aa9fd6672..acebb19a929 100644 --- a/src/NHibernate.Test/QueryTest/MultiCriteriaFixture.cs +++ b/src/NHibernate.Test/QueryTest/MultiCriteriaFixture.cs @@ -127,7 +127,7 @@ public void CanUseSecondLevelCacheWithPositionalParameters() CreateItems(); - DoMutiQueryAndAssert(); + DoMultiQueryAndAssert(); Assert.AreEqual(1, cacheHashtable.Count); } @@ -136,20 +136,20 @@ public void CanUseSecondLevelCacheWithPositionalParameters() public void CanGetMultiQueryFromSecondLevelCache() { CreateItems(); - //set the query in the cache - DoMutiQueryAndAssert(); + // Set the query in the cache. + DoMultiQueryAndAssert(); var cacheHashtable = MultipleQueriesFixture.GetHashTableUsedAsQueryCache(Sfi); - var cachedListEntry = (IList)new ArrayList(cacheHashtable.Values)[0]; + var cachedListEntry = (IList) new ArrayList(cacheHashtable.Values)[0]; // The first element is a timestamp, then only we have the cached data. - var cachedQuery = (IList)cachedListEntry[1] ?? throw new InvalidOperationException("Cached data is null"); + var cachedQuery = (IList) cachedListEntry[1] ?? throw new InvalidOperationException("Cached data is null"); - var firstQueryResults = (IList)cachedQuery[0]; + var firstQueryResults = (IList) cachedQuery[0]; firstQueryResults.Clear(); firstQueryResults.Add(3); firstQueryResults.Add(4); - var secondQueryResults = (IList)cachedQuery[1]; + var secondQueryResults = (IList) cachedQuery[1]; secondQueryResults[0] = 2; using (var s = Sfi.OpenSession()) @@ -161,9 +161,9 @@ public void CanGetMultiQueryFromSecondLevelCache() .Add(CriteriaTransformer.Clone(criteria).SetProjection(Projections.RowCount())); multiCriteria.SetCacheable(true); var results = multiCriteria.List(); - var items = (IList)results[0]; + var items = (IList) results[0]; Assert.AreEqual(2, items.Count); - var count = (int)((IList)results[1])[0]; + var count = (int) ((IList) results[1])[0]; Assert.AreEqual(2L, count); } } @@ -173,12 +173,12 @@ public void CanUpdateStatisticsWhenGetMultiQueryFromSecondLevelCache() { CreateItems(); - DoMutiQueryAndAssert(); + DoMultiQueryAndAssert(); Assert.AreEqual(0, Sfi.Statistics.QueryCacheHitCount); Assert.AreEqual(1, Sfi.Statistics.QueryCacheMissCount); Assert.AreEqual(1, Sfi.Statistics.QueryCachePutCount); - DoMutiQueryAndAssert(); + DoMultiQueryAndAssert(); Assert.AreEqual(1, Sfi.Statistics.QueryCacheHitCount); Assert.AreEqual(1, Sfi.Statistics.QueryCacheMissCount); Assert.AreEqual(1, Sfi.Statistics.QueryCachePutCount); @@ -437,7 +437,7 @@ public void CanNotRetrieveDetachedCriteriaResultWithUnknownKey() } } - private void DoMutiQueryAndAssert() + private void DoMultiQueryAndAssert() { using (var s = OpenSession()) { @@ -448,9 +448,9 @@ private void DoMutiQueryAndAssert() .Add(CriteriaTransformer.Clone(criteria).SetProjection(Projections.RowCount())); multiCriteria.SetCacheable(true); var results = multiCriteria.List(); - var items = (IList)results[0]; + var items = (IList) results[0]; Assert.AreEqual(89, items.Count); - var count = (int)((IList)results[1])[0]; + var count = (int) ((IList) results[1])[0]; Assert.AreEqual(99L, count); } } diff --git a/src/NHibernate.Test/QueryTest/MultipleMixedQueriesFixture.cs b/src/NHibernate.Test/QueryTest/MultipleMixedQueriesFixture.cs index 80fa0fc472f..cfd26a9efac 100644 --- a/src/NHibernate.Test/QueryTest/MultipleMixedQueriesFixture.cs +++ b/src/NHibernate.Test/QueryTest/MultipleMixedQueriesFixture.cs @@ -66,20 +66,20 @@ public void NH_1085_WillGiveReasonableErrorIfBadParameterName() public void CanGetMultiQueryFromSecondLevelCache() { CreateItems(); - //set the query in the cache - DoMutiQueryAndAssert(); + // Set the query in the cache. + DoMultiQueryAndAssert(); var cacheHashtable = MultipleQueriesFixture.GetHashTableUsedAsQueryCache(Sfi); - var cachedListEntry = (IList)new ArrayList(cacheHashtable.Values)[0]; + var cachedListEntry = (IList) new ArrayList(cacheHashtable.Values)[0]; // The first element is a timestamp, then only we have the cached data. var cachedQuery = (IList) cachedListEntry[1] ?? throw new InvalidOperationException("Cached data is null"); - var firstQueryResults = (IList)cachedQuery[0]; + var firstQueryResults = (IList) cachedQuery[0]; firstQueryResults.Clear(); firstQueryResults.Add(3); firstQueryResults.Add(4); - var secondQueryResults = (IList)cachedQuery[1]; + var secondQueryResults = (IList) cachedQuery[1]; secondQueryResults[0] = 2L; using (var s = Sfi.OpenSession()) @@ -92,9 +92,9 @@ public void CanGetMultiQueryFromSecondLevelCache() .SetInt32(0, 50)); multiQuery.SetCacheable(true); var results = multiQuery.List(); - var items = (IList)results[0]; + var items = (IList) results[0]; Assert.AreEqual(2, items.Count); - var count = (long)((IList)results[1])[0]; + var count = (long) ((IList) results[1])[0]; Assert.AreEqual(2L, count); } } @@ -174,12 +174,12 @@ public void CanUseSecondLevelCacheWithPositionalParameters() CreateItems(); - DoMutiQueryAndAssert(); + DoMultiQueryAndAssert(); Assert.AreEqual(1, cacheHashtable.Count); } - private void DoMutiQueryAndAssert() + private void DoMultiQueryAndAssert() { using (var s = OpenSession()) { @@ -191,9 +191,9 @@ private void DoMutiQueryAndAssert() .SetInt32(0, 50)); multiQuery.SetCacheable(true); var results = multiQuery.List(); - var items = (IList)results[0]; + var items = (IList) results[0]; Assert.AreEqual(89, items.Count); - var count = (long)((IList)results[1])[0]; + var count = (long) ((IList) results[1])[0]; Assert.AreEqual(99L, count); } } diff --git a/src/NHibernate.Test/QueryTest/MultipleQueriesFixture.cs b/src/NHibernate.Test/QueryTest/MultipleQueriesFixture.cs index 8249ea90002..a4eb5e81b41 100644 --- a/src/NHibernate.Test/QueryTest/MultipleQueriesFixture.cs +++ b/src/NHibernate.Test/QueryTest/MultipleQueriesFixture.cs @@ -69,20 +69,20 @@ public void NH_1085_WillGiveReasonableErrorIfBadParameterName() public void CanGetMultiQueryFromSecondLevelCache() { CreateItems(); - //set the query in the cache - DoMutiQueryAndAssert(); + // Set the query in the cache. + DoMultiQueryAndAssert(); var cacheHashtable = GetHashTableUsedAsQueryCache(Sfi); - var cachedListEntry = (IList)new ArrayList(cacheHashtable.Values)[0]; + var cachedListEntry = (IList) new ArrayList(cacheHashtable.Values)[0]; // The first element is a timestamp, then only we have the cached data. var cachedQuery = (IList) cachedListEntry[1] ?? throw new InvalidOperationException("Cached data is null"); - var firstQueryResults = (IList)cachedQuery[0]; + var firstQueryResults = (IList) cachedQuery[0]; firstQueryResults.Clear(); firstQueryResults.Add(3); firstQueryResults.Add(4); - var secondQueryResults = (IList)cachedQuery[1]; + var secondQueryResults = (IList) cachedQuery[1]; secondQueryResults[0] = 2L; using (var s = Sfi.OpenSession()) @@ -95,9 +95,9 @@ public void CanGetMultiQueryFromSecondLevelCache() .SetInt32(0, 50)); multiQuery.SetCacheable(true); var results = multiQuery.List(); - var items = (IList)results[0]; + var items = (IList) results[0]; Assert.AreEqual(2, items.Count); - var count = (long)((IList)results[1])[0]; + var count = (long) ((IList) results[1])[0]; Assert.AreEqual(2L, count); } } @@ -176,12 +176,12 @@ public void CanUseSecondLevelCacheWithPositionalParameters() CreateItems(); - DoMutiQueryAndAssert(); + DoMultiQueryAndAssert(); Assert.AreEqual(1, cacheHashtable.Count); } - private void DoMutiQueryAndAssert() + private void DoMultiQueryAndAssert() { using (var s = OpenSession()) { @@ -193,9 +193,9 @@ private void DoMutiQueryAndAssert() .SetInt32(0, 50)); multiQuery.SetCacheable(true); var results = multiQuery.List(); - var items = (IList)results[0]; + var items = (IList) results[0]; Assert.AreEqual(89, items.Count); - var count = (long)((IList)results[1])[0]; + var count = (long) ((IList) results[1])[0]; Assert.AreEqual(99L, count); } } diff --git a/src/NHibernate.Test/SqlTest/Query/NativeSQLQueriesFixture.cs b/src/NHibernate.Test/SqlTest/Query/NativeSQLQueriesFixture.cs index 67304e2359d..cda6ba1cf0e 100644 --- a/src/NHibernate.Test/SqlTest/Query/NativeSQLQueriesFixture.cs +++ b/src/NHibernate.Test/SqlTest/Query/NativeSQLQueriesFixture.cs @@ -197,7 +197,7 @@ public void SQLQueryInterfaceCacheable() } [Test(Description = "GH-2904")] - public void CacheableScalarSQLQuery() + public void CacheableScalarSqlQuery() { Organization ifa = new Organization("IFA"); Organization jboss = new Organization("JBoss"); @@ -252,7 +252,7 @@ class ResultDto } [Test(Description = "GH-3169")] - public void CacheableScalarSQLQueryWithTransformer() + public void CacheableScalarSqlQueryWithTransformer() { Organization ifa = new Organization("IFA"); @@ -288,7 +288,34 @@ void AssertQuery(bool fromCache) } [Test(Description = "GH-3169")] - public void CacheableScalarSQLMultiQueryWithTransformer() + public void CacheableScalarSqlEmptyQueryWithTransformer() + { + void AssertQuery(bool fromCache) + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + using (EnableStatisticsScope()) + { + var l = s.CreateSQLQuery("select org.NAME as regionCode from ORGANIZATION org") + .AddScalar("regionCode", NHibernateUtil.String) + .SetResultTransformer(Transformers.AliasToBean()) + .SetCacheable(true) + .List(); + t.Commit(); + + Assert.That(l.Count, Is.EqualTo(0)); + var msg = "Results are expected from " + (fromCache ? "cache" : "DB"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(fromCache ? 0 : 1), msg); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(fromCache ? 1 : 0), msg); + } + } + + AssertQuery(false); + AssertQuery(true); + } + + [Test(Description = "GH-3169")] + public void CacheableScalarSqlMultiQueryWithTransformer() { Organization ifa = new Organization("IFA"); @@ -336,6 +363,46 @@ void AssertQuery(bool fromCache) AssertQuery(true); } + [Test(Description = "GH-3169")] + public void CacheableScalarSqlEmptyMultiQueryWithTransformer() + { + void AssertQuery(bool fromCache) + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + using (EnableStatisticsScope()) + { + var q1 = s.CreateSQLQuery("select org.NAME as regionCode from ORGANIZATION org") + .AddScalar("regionCode", NHibernateUtil.String) + .SetResultTransformer(Transformers.AliasToBean()) + .SetCacheable(true); + var q2 = s.CreateSQLQuery("select org.ORGID as orgId from ORGANIZATION org") + .AddScalar("orgId", NHibernateUtil.Int64) + .SetResultTransformer(Transformers.AliasToBean()) + .SetCacheable(true); + + var batch = s.CreateQueryBatch(); + batch.Add(q1); + batch.Add(q2); + batch.Execute(); + + var l1 = batch.GetResult(0); + var l2 = batch.GetResult(1); + + t.Commit(); + + Assert.That(l1.Count, Is.EqualTo(0), "Unexpected results count for the first query."); + Assert.That(l2.Count, Is.EqualTo(0), "Unexpected results count for the second query."); + var msg = "Results are expected from " + (fromCache ? "cache" : "DB"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(fromCache ? 0 : 2), msg); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(fromCache ? 2 : 0), msg); + } + } + + AssertQuery(false); + AssertQuery(true); + } + [Test] public void ResultSetMappingDefinition() { diff --git a/src/NHibernate/Async/Cache/StandardQueryCache.cs b/src/NHibernate/Async/Cache/StandardQueryCache.cs index d21e9a075ca..8bc97cbdd18 100644 --- a/src/NHibernate/Async/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Async/Cache/StandardQueryCache.cs @@ -214,22 +214,21 @@ public async Task GetManyAsync( continue; } + var timestamp = GetResultsMetadata(cacheable, out var aliases); var key = keys[i]; + if (key.ResultTransformer?.AutoDiscoverTypes == true && HasResults(cacheable)) + { + key.ResultTransformer.SupplyAutoDiscoveredParameters(queryParameters[i].ResultTransformer, aliases); + } + var querySpaces = spaces[i]; if (queryParameters[i].NaturalKeyLookup || querySpaces.Count == 0) continue; spacesToCheck.Add(querySpaces); checkedSpacesIndexes.Add(i); - var timestamp = GetResultsMetadata(cacheable, out var aliases); checkedSpacesTimestamp.Add(timestamp); if (Log.IsDebugEnabled()) Log.Debug("Checking query spaces for up-to-dateness [{0}]", StringHelper.CollectionToString(querySpaces)); - - var key = keys[i]; - if (key.ResultTransformer?.AutoDiscoverTypes == true && HasResults(cacheable)) - { - key.ResultTransformer.SupplyAutoDiscoveredParameters(queryParameters[i].ResultTransformer, aliases); - } } var upToDates = spacesToCheck.Count > 0 @@ -255,6 +254,7 @@ public async Task GetManyAsync( if (checkedSpacesIndexes.Contains(i) && !upToDates[upToDatesIndex++]) { Log.Debug("cached query results were not up to date for: {0}", key); + cacheables[i] = null; continue; } @@ -272,7 +272,7 @@ public async Task GetManyAsync( for (var i = 0; i < keys.Length; i++) { - if (finalReturnTypes[i] == null) + if (cacheables[i] == null) { continue; } @@ -295,7 +295,7 @@ public async Task GetManyAsync( for (var i = 0; i < keys.Length; i++) { - if (finalReturnTypes[i] == null) + if (cacheables[i] == null) { continue; } @@ -360,6 +360,9 @@ private static async Task PerformBeforeAssembleAsync( IList cacheable, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); + if (!HasResults(cacheable)) + return; + if (returnTypes.Length == 1) { var returnType = returnTypes[0]; @@ -389,6 +392,9 @@ private async Task PerformAssembleAsync( try { var result = new List(cacheable.Count - 1); + if (!HasResults(cacheable)) + return result; + if (returnTypes.Length == 1) { var returnType = returnTypes[0]; @@ -443,6 +449,9 @@ private static async Task InitializeCollectionsAsync( IList cacheResult, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); + if (!HasResults(cacheResult)) + return; + var collectionIndexes = new Dictionary(); for (var i = 0; i < returnTypes.Length; i++) { @@ -481,6 +490,10 @@ private async Task GetResultFromCacheableAsync( { cancellationToken.ThrowIfCancellationRequested(); Log.Debug("returning cached query results for: {0}", key); + + if (!HasResults(cacheable)) + return new List(); + returnTypes = GetReturnTypes(key, returnTypes, cacheable); try { diff --git a/src/NHibernate/Cache/StandardQueryCache.cs b/src/NHibernate/Cache/StandardQueryCache.cs index 877e99679cd..e556c6fa367 100644 --- a/src/NHibernate/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Cache/StandardQueryCache.cs @@ -249,22 +249,21 @@ public IList[] GetMany( continue; } + var timestamp = GetResultsMetadata(cacheable, out var aliases); var key = keys[i]; + if (key.ResultTransformer?.AutoDiscoverTypes == true && HasResults(cacheable)) + { + key.ResultTransformer.SupplyAutoDiscoveredParameters(queryParameters[i].ResultTransformer, aliases); + } + var querySpaces = spaces[i]; if (queryParameters[i].NaturalKeyLookup || querySpaces.Count == 0) continue; spacesToCheck.Add(querySpaces); checkedSpacesIndexes.Add(i); - var timestamp = GetResultsMetadata(cacheable, out var aliases); checkedSpacesTimestamp.Add(timestamp); if (Log.IsDebugEnabled()) Log.Debug("Checking query spaces for up-to-dateness [{0}]", StringHelper.CollectionToString(querySpaces)); - - var key = keys[i]; - if (key.ResultTransformer?.AutoDiscoverTypes == true && HasResults(cacheable)) - { - key.ResultTransformer.SupplyAutoDiscoveredParameters(queryParameters[i].ResultTransformer, aliases); - } } var upToDates = spacesToCheck.Count > 0 @@ -290,6 +289,7 @@ public IList[] GetMany( if (checkedSpacesIndexes.Contains(i) && !upToDates[upToDatesIndex++]) { Log.Debug("cached query results were not up to date for: {0}", key); + cacheables[i] = null; continue; } @@ -307,7 +307,7 @@ public IList[] GetMany( for (var i = 0; i < keys.Length; i++) { - if (finalReturnTypes[i] == null) + if (cacheables[i] == null) { continue; } @@ -330,7 +330,7 @@ public IList[] GetMany( for (var i = 0; i < keys.Length; i++) { - if (finalReturnTypes[i] == null) + if (cacheables[i] == null) { continue; } @@ -426,11 +426,17 @@ private static ICacheAssembler[] GetReturnTypes( ICacheAssembler[] returnTypes, IList cacheable) { - if (key.ResultTransformer?.AutoDiscoverTypes == true && HasResults(cacheable)) + var hasResults = HasResults(cacheable); + if (key.ResultTransformer?.AutoDiscoverTypes == true && hasResults) { returnTypes = GuessTypes(cacheable); } + if (hasResults && returnTypes == null) + { + throw new HibernateException("Return types for non empty query results are null, cannot assemble the results"); + } + return returnTypes; } @@ -439,6 +445,9 @@ private static void PerformBeforeAssemble( ISessionImplementor session, IList cacheable) { + if (!HasResults(cacheable)) + return; + if (returnTypes.Length == 1) { var returnType = returnTypes[0]; @@ -467,6 +476,9 @@ private IList PerformAssemble( try { var result = new List(cacheable.Count - 1); + if (!HasResults(cacheable)) + return result; + if (returnTypes.Length == 1) { var returnType = returnTypes[0]; @@ -520,6 +532,9 @@ private static void InitializeCollections( IList assembleResult, IList cacheResult) { + if (!HasResults(cacheResult)) + return; + var collectionIndexes = new Dictionary(); for (var i = 0; i < returnTypes.Length; i++) { @@ -557,6 +572,10 @@ private IList GetResultFromCacheable( IList cacheable) { Log.Debug("returning cached query results for: {0}", key); + + if (!HasResults(cacheable)) + return new List(); + returnTypes = GetReturnTypes(key, returnTypes, cacheable); try { diff --git a/src/NHibernate/Transform/CacheableResultTransformer.cs b/src/NHibernate/Transform/CacheableResultTransformer.cs index 1f05747082e..5c6b1001639 100644 --- a/src/NHibernate/Transform/CacheableResultTransformer.cs +++ b/src/NHibernate/Transform/CacheableResultTransformer.cs @@ -232,12 +232,14 @@ public object TransformTuple(object[] tuple, string[] aliases) /// The aliases that correspond to the untransformed tuple. /// The transformer for the re-transformation. /// - /// transformedResults, with each element re-transformed (if necessary). + /// , with each element re-transformed (if necessary). public IList RetransformResults(IList transformedResults, string[] aliases, IResultTransformer transformer, bool[] includeInTuple) { + if (transformedResults.Count == 0) + return transformedResults; if (transformer == null) throw new ArgumentNullException(nameof(transformer)); if (_includeInTuple == null) @@ -287,8 +289,7 @@ public IList RetransformResults(IList transformedResults, /// elements are replaced with untransformed values) and the original /// List is returned. /// - /// If not unnecessary, the original List is returned - /// unchanged. + /// If not necessary, the original List is returned unchanged. /// /// /// @@ -297,9 +298,12 @@ public IList RetransformResults(IList transformedResults, /// excluded tuple elements will be null. /// /// Results that were previously transformed. - /// results, with each element untransformed (if necessary). + /// , with each element untransformed (if necessary). public IList UntransformToTuples(IList results) { + if (results.Count == 0) + return results; + if (_includeInTuple == null) throw new InvalidOperationException("This transformer is not initialized"); if (_includeInTransformIndex == null) From 8bbd84ea4054f46ecb3e6cfe7f0793961d07626f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <12201973+fredericdelaporte@users.noreply.github.com> Date: Sun, 23 Oct 2022 18:56:14 +0200 Subject: [PATCH 17/22] Add a JsonSerializer test and fix it --- .../CacheTest/JsonSerializerCacheFixture.cs | 536 ++++++++++++++++++ .../CacheTest/JsonSerializerCacheFixture.cs | 525 +++++++++++++++++ src/NHibernate.Test/NHibernate.Test.csproj | 2 + .../Async/Cache/StandardQueryCache.cs | 2 +- src/NHibernate/Cache/StandardQueryCache.cs | 4 +- 5 files changed, 1066 insertions(+), 3 deletions(-) create mode 100644 src/NHibernate.Test/Async/CacheTest/JsonSerializerCacheFixture.cs create mode 100644 src/NHibernate.Test/CacheTest/JsonSerializerCacheFixture.cs diff --git a/src/NHibernate.Test/Async/CacheTest/JsonSerializerCacheFixture.cs b/src/NHibernate.Test/Async/CacheTest/JsonSerializerCacheFixture.cs new file mode 100644 index 00000000000..01403997a46 --- /dev/null +++ b/src/NHibernate.Test/Async/CacheTest/JsonSerializerCacheFixture.cs @@ -0,0 +1,536 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System; +using System.Collections.Generic; +using System.Linq; +using NHibernate.Caches.CoreDistributedCache; +using NHibernate.Caches.CoreDistributedCache.Memory; +using NHibernate.Caches.Util.JsonSerializer; +using NHibernate.Cfg; +using NHibernate.Linq; +using NHibernate.Multi; +using NHibernate.Transform; +using NUnit.Framework; +using Environment = NHibernate.Cfg.Environment; + +namespace NHibernate.Test.CacheTest +{ + using System.Threading.Tasks; + [TestFixture] + public class JsonSerializerCacheFixtureAsync : TestCase + { + protected override string[] Mappings => new[] + { + "CacheTest.ReadOnly.hbm.xml", + "CacheTest.ReadWrite.hbm.xml" + }; + + protected override string MappingsAssembly => "NHibernate.Test"; + + protected override void Configure(Configuration configuration) + { + configuration.SetProperty(Environment.UseSecondLevelCache, "true"); + configuration.SetProperty(Environment.UseQueryCache, "true"); + configuration.SetProperty(Environment.GenerateStatistics, "true"); + configuration.SetProperty(Environment.CacheProvider, typeof(CoreDistributedCacheProvider).AssemblyQualifiedName); + CoreDistributedCacheProvider.CacheFactory = new MemoryFactory(); + var serializer = new JsonCacheSerializer(); + serializer.RegisterType(typeof(Tuple), "tso"); + CoreDistributedCacheProvider.DefaultSerializer = serializer; + } + + protected override void OnSetUp() + { + using (var s = Sfi.OpenSession()) + using (var tx = s.BeginTransaction()) + { + var totalItems = 6; + for (var i = 1; i <= totalItems; i++) + { + var parent = new ReadOnly + { + Name = $"Name{i}" + }; + for (var j = 1; j <= totalItems; j++) + { + var child = new ReadOnlyItem + { + Parent = parent + }; + parent.Items.Add(child); + } + s.Save(parent); + } + for (var i = 1; i <= totalItems; i++) + { + var parent = new ReadWrite + { + Name = $"Name{i}" + }; + for (var j = 1; j <= totalItems; j++) + { + var child = new ReadWriteItem + { + Parent = parent + }; + parent.Items.Add(child); + } + s.Save(parent); + } + tx.Commit(); + } + } + + protected override void OnTearDown() + { + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + s.CreateQuery("delete from ReadOnlyItem").ExecuteUpdate(); + s.CreateQuery("delete from ReadWriteItem").ExecuteUpdate(); + s.CreateQuery("delete from ReadOnly").ExecuteUpdate(); + s.CreateQuery("delete from ReadWrite").ExecuteUpdate(); + tx.Commit(); + } + // Must rebuild the session factory, CoreDistribted cache being not clearable. + RebuildSessionFactory(); + } + + [Test] + public async Task CacheableScalarSqlQueryWithTransformerAsync() + { + async Task AssertQueryAsync(bool fromCache) + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + using (EnableStatisticsScope()) + { + var l = await (s.CreateSQLQuery("select ro.Name as RegionCode from ReadOnly ro") + .AddScalar("regionCode", NHibernateUtil.String) + .SetResultTransformer(Transformers.AliasToBean()) + .SetCacheable(true) + .ListAsync()); + await (t.CommitAsync()); + + Assert.That(l.Count, Is.EqualTo(6)); + var msg = "Results are expected from " + (fromCache ? "cache" : "DB"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(fromCache ? 0 : 1), msg); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(fromCache ? 1 : 0), msg); + } + } + + await (AssertQueryAsync(false)); + await (AssertQueryAsync(true)); + } + + [Test] + public async Task CacheableScalarSqlMultiQueryWithTransformerAsync() + { + async Task AssertQueryAsync(bool fromCache) + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + using (EnableStatisticsScope()) + { + var q1 = s.CreateSQLQuery("select rw.Name as RegionCode from ReadWrite rw") + .AddScalar("regionCode", NHibernateUtil.String) + .SetResultTransformer(Transformers.AliasToBean()) + .SetCacheable(true); + var q2 = s.CreateSQLQuery("select rw.Id as OrgId from ReadWrite rw") + .AddScalar("orgId", NHibernateUtil.Int64) + .SetResultTransformer(Transformers.AliasToBean()) + .SetCacheable(true); + + var batch = s.CreateQueryBatch(); + batch.Add(q1); + batch.Add(q2); + await (batch.ExecuteAsync()); + + var l1 = await (batch.GetResultAsync(0)); + var l2 = await (batch.GetResultAsync(1)); + + await (t.CommitAsync()); + + Assert.That(l1.Count, Is.EqualTo(6), "Unexpected results count for the first query."); + Assert.That(l2.Count, Is.EqualTo(6), "Unexpected results count for the second query."); + var msg = "Results are expected from " + (fromCache ? "cache" : "DB"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(fromCache ? 0 : 2), msg); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(fromCache ? 2 : 0), msg); + } + } + + await (AssertQueryAsync(false)); + await (AssertQueryAsync(true)); + } + + class ResultDto + { + public long OrgId { get; set; } + public string RegionCode { get; set; } + } + + [Test] + public async Task QueryCacheTestAsync() + { + // QueryCache batching is used by QueryBatch. + if (!Sfi.ConnectionProvider.Driver.SupportsMultipleQueries) + Assert.Ignore($"{Sfi.ConnectionProvider.Driver} does not support multiple queries"); + + Sfi.Statistics.Clear(); + + using var s = OpenSession(); + + const string query = "from ReadOnly e where e.Name = :name"; + const string name1 = "Name1"; + const string name2 = "Name2"; + const string name3 = "Name3"; + const string name4 = "Name4"; + const string name5 = "Name5"; + var q1 = + s + .CreateQuery(query) + .SetString("name", name1) + .SetCacheable(true); + var q2 = + s + .CreateQuery(query) + .SetString("name", name2) + .SetCacheable(true); + var q3 = + s + .Query() + .Where(r => r.Name == name3) + .WithOptions(o => o.SetCacheable(true)); + var q4 = + s + .QueryOver() + .Where(r => r.Name == name4) + .Cacheable(); + var q5 = + s + .CreateSQLQuery("select * " + query) + .AddEntity(typeof(ReadOnly)) + .SetString("name", name5) + .SetCacheable(true); + + var queries = + s + .CreateQueryBatch() + .Add(q1) + .Add(q2) + .Add(q3) + .Add(q4) + .Add(q5); + + using (var t = s.BeginTransaction()) + { + await (queries.ExecuteAsync()); + await (t.CommitAsync()); + } + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "queries first execution count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(5), "cache misses first execution"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(0), "cache hits first execution"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(5), "cache puts first execution"); + + // Run a second time, to test the query cache + using (var t = s.BeginTransaction()) + { + await (queries.ExecuteAsync()); + await (t.CommitAsync()); + } + + Assert.That( + await (queries.GetResultAsync(0)), + Has.Count.EqualTo(1).And.One.Property(nameof(ReadOnly.Name)).EqualTo(name1), "q1"); + Assert.That( + await (queries.GetResultAsync(1)), + Has.Count.EqualTo(1).And.One.Property(nameof(ReadOnly.Name)).EqualTo(name2), "q2"); + Assert.That( + await (queries.GetResultAsync(2)), + Has.Count.EqualTo(1).And.One.Property(nameof(ReadWrite.Name)).EqualTo(name3), "q3"); + Assert.That( + await (queries.GetResultAsync(3)), + Has.Count.EqualTo(1).And.One.Property(nameof(ReadWrite.Name)).EqualTo(name4), "q4"); + Assert.That( + await (queries.GetResultAsync(4)), + Has.Count.EqualTo(1).And.One.Property(nameof(ReadOnly.Name)).EqualTo(name5), "q5"); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "queries second execution count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(5), "cache misses second execution"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(5), "cache hits second execution"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(5), "cache puts second execution"); + + // Update an entity to invalidate them + using (var t = s.BeginTransaction()) + { + var readwrite1 = await (s.Query().SingleAsync(e => e.Name == name3)); + readwrite1.Name = "NewName"; + await (t.CommitAsync()); + } + + // Run a third time, to re-test the query cache + using (var t = s.BeginTransaction()) + { + await (queries.ExecuteAsync()); + await (t.CommitAsync()); + } + + Assert.That( + await (queries.GetResultAsync(0)), + Has.Count.EqualTo(1).And.One.Property(nameof(ReadOnly.Name)).EqualTo(name1), "q1 after update"); + Assert.That( + await (queries.GetResultAsync(1)), + Has.Count.EqualTo(1).And.One.Property(nameof(ReadOnly.Name)).EqualTo(name2), "q2 after update"); + Assert.That( + await (queries.GetResultAsync(2)), + Has.Count.EqualTo(0), "q3 after update"); + Assert.That( + await (queries.GetResultAsync(3)), + Has.Count.EqualTo(1).And.One.Property(nameof(ReadWrite.Name)).EqualTo(name4), "q4 after update"); + Assert.That( + await (queries.GetResultAsync(4)), + Has.Count.EqualTo(1).And.One.Property(nameof(ReadOnly.Name)).EqualTo(name5), "q5 after update"); + + // The two ReadWrite queries should have been re-executed, so count should have been incremented accordingly. + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(3), "queries third execution count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(7), "cache misses third execution"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(8), "cache hits third execution"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(7), "cache puts third execution"); + } + + [Test] + public async Task QueryEntityBatchCacheTestAsync() + { + Sfi.Statistics.Clear(); + + List items; + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + items = await (s.Query() + .WithOptions(o => o.SetCacheable(true)) + .ToListAsync()); + + await (tx.CommitAsync()); + } + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "query first execution count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(1), "cache misses first execution"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(0), "cache hits first execution"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "cache puts first execution"); + + Sfi.Statistics.Clear(); + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + items = await (s.Query() + .WithOptions(o => o.SetCacheable(true)) + .ToListAsync()); + + await (tx.CommitAsync()); + } + + Assert.That(items, Has.Count.EqualTo(36)); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(0), "query second execution count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "cache misses second execution"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "cache hits second execution"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "cache puts second execution"); + } + + [TestCase(false)] + [TestCase(true)] + public async Task QueryFetchCollectionBatchCacheTestAsync(bool future) + { + if (future && !Sfi.ConnectionProvider.Driver.SupportsMultipleQueries) + { + Assert.Ignore($"{Sfi.ConnectionProvider.Driver} does not support multiple queries"); + } + + int middleId; + + using (var s = OpenSession()) + { + var ids = await (s.Query().Select(o => o.Id).OrderBy(o => o).ToListAsync()); + middleId = ids[2]; + } + + Sfi.Statistics.Clear(); + + List items; + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + if (future) + { + s.Query() + .WithOptions(o => o.SetCacheable(true)) + .FetchMany(o => o.Items) + .Where(o => o.Id > middleId) + .ToFuture(); + + items = s.Query() + .WithOptions(o => o.SetCacheable(true)) + .FetchMany(o => o.Items) + .Where(o => o.Id <= middleId) + .ToFuture() + .ToList(); + } + else + { + items = await (s.Query() + .WithOptions(o => o.SetCacheable(true)) + .FetchMany(o => o.Items) + .ToListAsync()); + } + + await (tx.CommitAsync()); + } + + Assert.That(items, Has.Count.EqualTo(future ? 3 : 6), "Unexpected items count"); + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(future ? 2 : 1), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(future ? 2 : 1), "Unexpected cache miss count"); + + Sfi.Statistics.Clear(); + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + if (future) + { + s.Query() + .WithOptions(o => o.SetCacheable(true)) + .FetchMany(o => o.Items) + .Where(o => o.Id > middleId) + .ToFuture(); + + items = s.Query() + .WithOptions(o => o.SetCacheable(true)) + .FetchMany(o => o.Items) + .Where(o => o.Id <= middleId) + .ToFuture() + .ToList(); + } + else + { + items = await (s.Query() + .WithOptions(o => o.SetCacheable(true)) + .FetchMany(o => o.Items) + .ToListAsync()); + } + + await (tx.CommitAsync()); + } + + Assert.That(items, Has.Count.EqualTo(future ? 3 : 6)); + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(0), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "Unexpected cache miss count"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(future ? 2 : 1), "Unexpected cache hit count"); + } + + [TestCase(false)] + [TestCase(true)] + public async Task QueryFetchEntityBatchCacheTestAsync(bool future) + { + if (future && !Sfi.ConnectionProvider.Driver.SupportsMultipleQueries) + { + Assert.Ignore($"{Sfi.ConnectionProvider.Driver} does not support multiple queries"); + } + + int middleId; + + using (var s = OpenSession()) + { + var ids = await (s.Query().Select(o => o.Id).OrderBy(o => o).ToListAsync()); + middleId = ids[17]; + } + + Sfi.Statistics.Clear(); + + List items; + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + if (future) + { + s.Query() + .WithOptions(o => o.SetCacheable(true)) + .Fetch(o => o.Parent) + .Where(o => o.Id > middleId) + .ToFuture(); + + items = s.Query() + .WithOptions(o => o.SetCacheable(true)) + .Fetch(o => o.Parent) + .Where(o => o.Id <= middleId) + .ToFuture() + .ToList(); + } + else + { + items = await (s.Query() + .WithOptions(o => o.SetCacheable(true)) + .Fetch(o => o.Parent) + .ToListAsync()); + } + + await (tx.CommitAsync()); + } + + Assert.That(items, Has.Count.EqualTo(future ? 18 : 36), "Unexpected items count"); + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(future ? 2 : 1), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(future ? 2 : 1), "Unexpected cache miss count"); + + Sfi.Statistics.Clear(); + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + if (future) + { + s.Query() + .WithOptions(o => o.SetCacheable(true)) + .Fetch(o => o.Parent) + .Where(o => o.Id > middleId) + .ToFuture(); + + items = s.Query() + .WithOptions(o => o.SetCacheable(true)) + .Fetch(o => o.Parent) + .Where(o => o.Id <= middleId) + .ToFuture() + .ToList(); + } + else + { + items = await (s.Query() + .WithOptions(o => o.SetCacheable(true)) + .Fetch(o => o.Parent) + .ToListAsync()); + } + + await (tx.CommitAsync()); + } + + Assert.That(items, Has.Count.EqualTo(future ? 18 : 36)); + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(0), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "Unexpected cache miss count"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(future ? 2 : 1), "Unexpected cache hit count"); + } + } +} diff --git a/src/NHibernate.Test/CacheTest/JsonSerializerCacheFixture.cs b/src/NHibernate.Test/CacheTest/JsonSerializerCacheFixture.cs new file mode 100644 index 00000000000..bf1009c90d3 --- /dev/null +++ b/src/NHibernate.Test/CacheTest/JsonSerializerCacheFixture.cs @@ -0,0 +1,525 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NHibernate.Caches.CoreDistributedCache; +using NHibernate.Caches.CoreDistributedCache.Memory; +using NHibernate.Caches.Util.JsonSerializer; +using NHibernate.Cfg; +using NHibernate.Linq; +using NHibernate.Multi; +using NHibernate.Transform; +using NUnit.Framework; +using Environment = NHibernate.Cfg.Environment; + +namespace NHibernate.Test.CacheTest +{ + [TestFixture] + public class JsonSerializerCacheFixture : TestCase + { + protected override string[] Mappings => new[] + { + "CacheTest.ReadOnly.hbm.xml", + "CacheTest.ReadWrite.hbm.xml" + }; + + protected override string MappingsAssembly => "NHibernate.Test"; + + protected override void Configure(Configuration configuration) + { + configuration.SetProperty(Environment.UseSecondLevelCache, "true"); + configuration.SetProperty(Environment.UseQueryCache, "true"); + configuration.SetProperty(Environment.GenerateStatistics, "true"); + configuration.SetProperty(Environment.CacheProvider, typeof(CoreDistributedCacheProvider).AssemblyQualifiedName); + CoreDistributedCacheProvider.CacheFactory = new MemoryFactory(); + var serializer = new JsonCacheSerializer(); + serializer.RegisterType(typeof(Tuple), "tso"); + CoreDistributedCacheProvider.DefaultSerializer = serializer; + } + + protected override void OnSetUp() + { + using (var s = Sfi.OpenSession()) + using (var tx = s.BeginTransaction()) + { + var totalItems = 6; + for (var i = 1; i <= totalItems; i++) + { + var parent = new ReadOnly + { + Name = $"Name{i}" + }; + for (var j = 1; j <= totalItems; j++) + { + var child = new ReadOnlyItem + { + Parent = parent + }; + parent.Items.Add(child); + } + s.Save(parent); + } + for (var i = 1; i <= totalItems; i++) + { + var parent = new ReadWrite + { + Name = $"Name{i}" + }; + for (var j = 1; j <= totalItems; j++) + { + var child = new ReadWriteItem + { + Parent = parent + }; + parent.Items.Add(child); + } + s.Save(parent); + } + tx.Commit(); + } + } + + protected override void OnTearDown() + { + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + s.CreateQuery("delete from ReadOnlyItem").ExecuteUpdate(); + s.CreateQuery("delete from ReadWriteItem").ExecuteUpdate(); + s.CreateQuery("delete from ReadOnly").ExecuteUpdate(); + s.CreateQuery("delete from ReadWrite").ExecuteUpdate(); + tx.Commit(); + } + // Must rebuild the session factory, CoreDistribted cache being not clearable. + RebuildSessionFactory(); + } + + [Test] + public void CacheableScalarSqlQueryWithTransformer() + { + void AssertQuery(bool fromCache) + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + using (EnableStatisticsScope()) + { + var l = s.CreateSQLQuery("select ro.Name as RegionCode from ReadOnly ro") + .AddScalar("regionCode", NHibernateUtil.String) + .SetResultTransformer(Transformers.AliasToBean()) + .SetCacheable(true) + .List(); + t.Commit(); + + Assert.That(l.Count, Is.EqualTo(6)); + var msg = "Results are expected from " + (fromCache ? "cache" : "DB"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(fromCache ? 0 : 1), msg); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(fromCache ? 1 : 0), msg); + } + } + + AssertQuery(false); + AssertQuery(true); + } + + [Test] + public void CacheableScalarSqlMultiQueryWithTransformer() + { + void AssertQuery(bool fromCache) + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + using (EnableStatisticsScope()) + { + var q1 = s.CreateSQLQuery("select rw.Name as RegionCode from ReadWrite rw") + .AddScalar("regionCode", NHibernateUtil.String) + .SetResultTransformer(Transformers.AliasToBean()) + .SetCacheable(true); + var q2 = s.CreateSQLQuery("select rw.Id as OrgId from ReadWrite rw") + .AddScalar("orgId", NHibernateUtil.Int64) + .SetResultTransformer(Transformers.AliasToBean()) + .SetCacheable(true); + + var batch = s.CreateQueryBatch(); + batch.Add(q1); + batch.Add(q2); + batch.Execute(); + + var l1 = batch.GetResult(0); + var l2 = batch.GetResult(1); + + t.Commit(); + + Assert.That(l1.Count, Is.EqualTo(6), "Unexpected results count for the first query."); + Assert.That(l2.Count, Is.EqualTo(6), "Unexpected results count for the second query."); + var msg = "Results are expected from " + (fromCache ? "cache" : "DB"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(fromCache ? 0 : 2), msg); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(fromCache ? 2 : 0), msg); + } + } + + AssertQuery(false); + AssertQuery(true); + } + + class ResultDto + { + public long OrgId { get; set; } + public string RegionCode { get; set; } + } + + [Test] + public void QueryCacheTest() + { + // QueryCache batching is used by QueryBatch. + if (!Sfi.ConnectionProvider.Driver.SupportsMultipleQueries) + Assert.Ignore($"{Sfi.ConnectionProvider.Driver} does not support multiple queries"); + + Sfi.Statistics.Clear(); + + using var s = OpenSession(); + + const string query = "from ReadOnly e where e.Name = :name"; + const string name1 = "Name1"; + const string name2 = "Name2"; + const string name3 = "Name3"; + const string name4 = "Name4"; + const string name5 = "Name5"; + var q1 = + s + .CreateQuery(query) + .SetString("name", name1) + .SetCacheable(true); + var q2 = + s + .CreateQuery(query) + .SetString("name", name2) + .SetCacheable(true); + var q3 = + s + .Query() + .Where(r => r.Name == name3) + .WithOptions(o => o.SetCacheable(true)); + var q4 = + s + .QueryOver() + .Where(r => r.Name == name4) + .Cacheable(); + var q5 = + s + .CreateSQLQuery("select * " + query) + .AddEntity(typeof(ReadOnly)) + .SetString("name", name5) + .SetCacheable(true); + + var queries = + s + .CreateQueryBatch() + .Add(q1) + .Add(q2) + .Add(q3) + .Add(q4) + .Add(q5); + + using (var t = s.BeginTransaction()) + { + queries.Execute(); + t.Commit(); + } + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "queries first execution count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(5), "cache misses first execution"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(0), "cache hits first execution"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(5), "cache puts first execution"); + + // Run a second time, to test the query cache + using (var t = s.BeginTransaction()) + { + queries.Execute(); + t.Commit(); + } + + Assert.That( + queries.GetResult(0), + Has.Count.EqualTo(1).And.One.Property(nameof(ReadOnly.Name)).EqualTo(name1), "q1"); + Assert.That( + queries.GetResult(1), + Has.Count.EqualTo(1).And.One.Property(nameof(ReadOnly.Name)).EqualTo(name2), "q2"); + Assert.That( + queries.GetResult(2), + Has.Count.EqualTo(1).And.One.Property(nameof(ReadWrite.Name)).EqualTo(name3), "q3"); + Assert.That( + queries.GetResult(3), + Has.Count.EqualTo(1).And.One.Property(nameof(ReadWrite.Name)).EqualTo(name4), "q4"); + Assert.That( + queries.GetResult(4), + Has.Count.EqualTo(1).And.One.Property(nameof(ReadOnly.Name)).EqualTo(name5), "q5"); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "queries second execution count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(5), "cache misses second execution"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(5), "cache hits second execution"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(5), "cache puts second execution"); + + // Update an entity to invalidate them + using (var t = s.BeginTransaction()) + { + var readwrite1 = s.Query().Single(e => e.Name == name3); + readwrite1.Name = "NewName"; + t.Commit(); + } + + // Run a third time, to re-test the query cache + using (var t = s.BeginTransaction()) + { + queries.Execute(); + t.Commit(); + } + + Assert.That( + queries.GetResult(0), + Has.Count.EqualTo(1).And.One.Property(nameof(ReadOnly.Name)).EqualTo(name1), "q1 after update"); + Assert.That( + queries.GetResult(1), + Has.Count.EqualTo(1).And.One.Property(nameof(ReadOnly.Name)).EqualTo(name2), "q2 after update"); + Assert.That( + queries.GetResult(2), + Has.Count.EqualTo(0), "q3 after update"); + Assert.That( + queries.GetResult(3), + Has.Count.EqualTo(1).And.One.Property(nameof(ReadWrite.Name)).EqualTo(name4), "q4 after update"); + Assert.That( + queries.GetResult(4), + Has.Count.EqualTo(1).And.One.Property(nameof(ReadOnly.Name)).EqualTo(name5), "q5 after update"); + + // The two ReadWrite queries should have been re-executed, so count should have been incremented accordingly. + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(3), "queries third execution count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(7), "cache misses third execution"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(8), "cache hits third execution"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(7), "cache puts third execution"); + } + + [Test] + public void QueryEntityBatchCacheTest() + { + Sfi.Statistics.Clear(); + + List items; + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + items = s.Query() + .WithOptions(o => o.SetCacheable(true)) + .ToList(); + + tx.Commit(); + } + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "query first execution count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(1), "cache misses first execution"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(0), "cache hits first execution"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "cache puts first execution"); + + Sfi.Statistics.Clear(); + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + items = s.Query() + .WithOptions(o => o.SetCacheable(true)) + .ToList(); + + tx.Commit(); + } + + Assert.That(items, Has.Count.EqualTo(36)); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(0), "query second execution count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "cache misses second execution"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "cache hits second execution"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "cache puts second execution"); + } + + [TestCase(false)] + [TestCase(true)] + public void QueryFetchCollectionBatchCacheTest(bool future) + { + if (future && !Sfi.ConnectionProvider.Driver.SupportsMultipleQueries) + { + Assert.Ignore($"{Sfi.ConnectionProvider.Driver} does not support multiple queries"); + } + + int middleId; + + using (var s = OpenSession()) + { + var ids = s.Query().Select(o => o.Id).OrderBy(o => o).ToList(); + middleId = ids[2]; + } + + Sfi.Statistics.Clear(); + + List items; + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + if (future) + { + s.Query() + .WithOptions(o => o.SetCacheable(true)) + .FetchMany(o => o.Items) + .Where(o => o.Id > middleId) + .ToFuture(); + + items = s.Query() + .WithOptions(o => o.SetCacheable(true)) + .FetchMany(o => o.Items) + .Where(o => o.Id <= middleId) + .ToFuture() + .ToList(); + } + else + { + items = s.Query() + .WithOptions(o => o.SetCacheable(true)) + .FetchMany(o => o.Items) + .ToList(); + } + + tx.Commit(); + } + + Assert.That(items, Has.Count.EqualTo(future ? 3 : 6), "Unexpected items count"); + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(future ? 2 : 1), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(future ? 2 : 1), "Unexpected cache miss count"); + + Sfi.Statistics.Clear(); + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + if (future) + { + s.Query() + .WithOptions(o => o.SetCacheable(true)) + .FetchMany(o => o.Items) + .Where(o => o.Id > middleId) + .ToFuture(); + + items = s.Query() + .WithOptions(o => o.SetCacheable(true)) + .FetchMany(o => o.Items) + .Where(o => o.Id <= middleId) + .ToFuture() + .ToList(); + } + else + { + items = s.Query() + .WithOptions(o => o.SetCacheable(true)) + .FetchMany(o => o.Items) + .ToList(); + } + + tx.Commit(); + } + + Assert.That(items, Has.Count.EqualTo(future ? 3 : 6)); + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(0), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "Unexpected cache miss count"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(future ? 2 : 1), "Unexpected cache hit count"); + } + + [TestCase(false)] + [TestCase(true)] + public void QueryFetchEntityBatchCacheTest(bool future) + { + if (future && !Sfi.ConnectionProvider.Driver.SupportsMultipleQueries) + { + Assert.Ignore($"{Sfi.ConnectionProvider.Driver} does not support multiple queries"); + } + + int middleId; + + using (var s = OpenSession()) + { + var ids = s.Query().Select(o => o.Id).OrderBy(o => o).ToList(); + middleId = ids[17]; + } + + Sfi.Statistics.Clear(); + + List items; + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + if (future) + { + s.Query() + .WithOptions(o => o.SetCacheable(true)) + .Fetch(o => o.Parent) + .Where(o => o.Id > middleId) + .ToFuture(); + + items = s.Query() + .WithOptions(o => o.SetCacheable(true)) + .Fetch(o => o.Parent) + .Where(o => o.Id <= middleId) + .ToFuture() + .ToList(); + } + else + { + items = s.Query() + .WithOptions(o => o.SetCacheable(true)) + .Fetch(o => o.Parent) + .ToList(); + } + + tx.Commit(); + } + + Assert.That(items, Has.Count.EqualTo(future ? 18 : 36), "Unexpected items count"); + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(future ? 2 : 1), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(future ? 2 : 1), "Unexpected cache miss count"); + + Sfi.Statistics.Clear(); + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + if (future) + { + s.Query() + .WithOptions(o => o.SetCacheable(true)) + .Fetch(o => o.Parent) + .Where(o => o.Id > middleId) + .ToFuture(); + + items = s.Query() + .WithOptions(o => o.SetCacheable(true)) + .Fetch(o => o.Parent) + .Where(o => o.Id <= middleId) + .ToFuture() + .ToList(); + } + else + { + items = s.Query() + .WithOptions(o => o.SetCacheable(true)) + .Fetch(o => o.Parent) + .ToList(); + } + + tx.Commit(); + } + + Assert.That(items, Has.Count.EqualTo(future ? 18 : 36)); + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(0), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "Unexpected cache miss count"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(future ? 2 : 1), "Unexpected cache hit count"); + } + } +} diff --git a/src/NHibernate.Test/NHibernate.Test.csproj b/src/NHibernate.Test/NHibernate.Test.csproj index eb38a252a3a..c32dfd7d6e2 100644 --- a/src/NHibernate.Test/NHibernate.Test.csproj +++ b/src/NHibernate.Test/NHibernate.Test.csproj @@ -57,6 +57,8 @@ + + diff --git a/src/NHibernate/Async/Cache/StandardQueryCache.cs b/src/NHibernate/Async/Cache/StandardQueryCache.cs index 8bc97cbdd18..3c537f1f540 100644 --- a/src/NHibernate/Async/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Async/Cache/StandardQueryCache.cs @@ -336,7 +336,7 @@ private static async Task> GetCacheableResultAsync( var cacheable = new List(result.Count + 1) { - aliases == null ? ts : new object[] { ts, aliases } + aliases == null ? ts : new object[] { ts, aliases.ToArray() } }; foreach (var row in result) diff --git a/src/NHibernate/Cache/StandardQueryCache.cs b/src/NHibernate/Cache/StandardQueryCache.cs index e556c6fa367..adcae15b737 100644 --- a/src/NHibernate/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Cache/StandardQueryCache.cs @@ -376,7 +376,7 @@ private static List GetCacheableResult( var cacheable = new List(result.Count + 1) { - aliases == null ? ts : new object[] { ts, aliases } + aliases == null ? ts : new object[] { ts, aliases.ToArray() } }; foreach (var row in result) @@ -404,7 +404,7 @@ private static long GetResultsMetadata(IList cacheable, out string[] aliases) return timestamp.Value; var metadataArray = (object[]) metadata; - aliases = (string[]) metadataArray[1]; + aliases = ((object[]) metadataArray[1]).Cast().ToArray(); return (long) metadataArray[0]; } From 8232bcf7a3f617a76b5b15efd88f395ccf38fbbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <12201973+fredericDelaporte@users.noreply.github.com> Date: Sun, 23 Oct 2022 20:14:17 +0200 Subject: [PATCH 18/22] Simplify metadata extraction Co-authored-by: Roman Artiukhin --- src/NHibernate/Cache/StandardQueryCache.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/NHibernate/Cache/StandardQueryCache.cs b/src/NHibernate/Cache/StandardQueryCache.cs index adcae15b737..ac4fe8e67b6 100644 --- a/src/NHibernate/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Cache/StandardQueryCache.cs @@ -399,9 +399,8 @@ private static long GetResultsMetadata(IList cacheable, out string[] aliases) aliases = null; var metadata = cacheable[0]; - var timestamp = metadata as long?; - if (timestamp.HasValue) - return timestamp.Value; + if (metadata is long timestamp) + return timestamp; var metadataArray = (object[]) metadata; aliases = ((object[]) metadataArray[1]).Cast().ToArray(); From c07c6aff8a18e10fc4535b225ae9f5417b50948e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <12201973+fredericdelaporte@users.noreply.github.com> Date: Sun, 23 Oct 2022 20:38:58 +0200 Subject: [PATCH 19/22] Add test for multi-columns cached queries --- .../SqlTest/Query/NativeSQLQueriesFixture.cs | 43 ++++++++++++++++++- .../SqlTest/Query/NativeSQLQueriesFixture.cs | 43 ++++++++++++++++++- 2 files changed, 82 insertions(+), 4 deletions(-) diff --git a/src/NHibernate.Test/Async/SqlTest/Query/NativeSQLQueriesFixture.cs b/src/NHibernate.Test/Async/SqlTest/Query/NativeSQLQueriesFixture.cs index e84422afc12..6beefe2afa8 100644 --- a/src/NHibernate.Test/Async/SqlTest/Query/NativeSQLQueriesFixture.cs +++ b/src/NHibernate.Test/Async/SqlTest/Query/NativeSQLQueriesFixture.cs @@ -354,8 +354,9 @@ async Task AssertQueryAsync(bool fromCache) .AddScalar("regionCode", NHibernateUtil.String) .SetResultTransformer(Transformers.AliasToBean()) .SetCacheable(true); - var q2 = s.CreateSQLQuery("select org.ORGID as orgId from ORGANIZATION org") + var q2 = s.CreateSQLQuery("select org.ORGID as orgId, org.NAME as regionCode from ORGANIZATION org") .AddScalar("orgId", NHibernateUtil.Int64) + .AddScalar("regionCode", NHibernateUtil.String) .SetResultTransformer(Transformers.AliasToBean()) .SetCacheable(true); @@ -394,8 +395,9 @@ async Task AssertQueryAsync(bool fromCache) .AddScalar("regionCode", NHibernateUtil.String) .SetResultTransformer(Transformers.AliasToBean()) .SetCacheable(true); - var q2 = s.CreateSQLQuery("select org.ORGID as orgId from ORGANIZATION org") + var q2 = s.CreateSQLQuery("select org.ORGID as orgId, org.NAME as regionCode from ORGANIZATION org") .AddScalar("orgId", NHibernateUtil.Int64) + .AddScalar("regionCode", NHibernateUtil.String) .SetResultTransformer(Transformers.AliasToBean()) .SetCacheable(true); @@ -421,6 +423,43 @@ async Task AssertQueryAsync(bool fromCache) await (AssertQueryAsync(true)); } + [Test(Description = "GH-3169")] + public async Task CacheableMultiScalarSqlQueryWithTransformerAsync() + { + Organization ifa = new Organization("IFA"); + + using (ISession s = OpenSession()) + using (ITransaction t = s.BeginTransaction()) + { + await (s.SaveAsync(ifa)); + await (t.CommitAsync()); + } + + async Task AssertQueryAsync(bool fromCache) + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + using (EnableStatisticsScope()) + { + var l = await (s.CreateSQLQuery("select org.ORGID as orgId, org.NAME as regionCode from ORGANIZATION org") + .AddScalar("orgId", NHibernateUtil.Int64) + .AddScalar("regionCode", NHibernateUtil.String) + .SetResultTransformer(Transformers.AliasToBean()) + .SetCacheable(true) + .ListAsync()); + await (t.CommitAsync()); + + Assert.That(l.Count, Is.EqualTo(1)); + var msg = "Results are expected from " + (fromCache ? "cache" : "DB"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(fromCache ? 0 : 1), msg); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(fromCache ? 1 : 0), msg); + } + } + + await (AssertQueryAsync(false)); + await (AssertQueryAsync(true)); + } + [Test] public async Task ResultSetMappingDefinitionAsync() { diff --git a/src/NHibernate.Test/SqlTest/Query/NativeSQLQueriesFixture.cs b/src/NHibernate.Test/SqlTest/Query/NativeSQLQueriesFixture.cs index cda6ba1cf0e..38c395298df 100644 --- a/src/NHibernate.Test/SqlTest/Query/NativeSQLQueriesFixture.cs +++ b/src/NHibernate.Test/SqlTest/Query/NativeSQLQueriesFixture.cs @@ -336,8 +336,9 @@ void AssertQuery(bool fromCache) .AddScalar("regionCode", NHibernateUtil.String) .SetResultTransformer(Transformers.AliasToBean()) .SetCacheable(true); - var q2 = s.CreateSQLQuery("select org.ORGID as orgId from ORGANIZATION org") + var q2 = s.CreateSQLQuery("select org.ORGID as orgId, org.NAME as regionCode from ORGANIZATION org") .AddScalar("orgId", NHibernateUtil.Int64) + .AddScalar("regionCode", NHibernateUtil.String) .SetResultTransformer(Transformers.AliasToBean()) .SetCacheable(true); @@ -376,8 +377,9 @@ void AssertQuery(bool fromCache) .AddScalar("regionCode", NHibernateUtil.String) .SetResultTransformer(Transformers.AliasToBean()) .SetCacheable(true); - var q2 = s.CreateSQLQuery("select org.ORGID as orgId from ORGANIZATION org") + var q2 = s.CreateSQLQuery("select org.ORGID as orgId, org.NAME as regionCode from ORGANIZATION org") .AddScalar("orgId", NHibernateUtil.Int64) + .AddScalar("regionCode", NHibernateUtil.String) .SetResultTransformer(Transformers.AliasToBean()) .SetCacheable(true); @@ -403,6 +405,43 @@ void AssertQuery(bool fromCache) AssertQuery(true); } + [Test(Description = "GH-3169")] + public void CacheableMultiScalarSqlQueryWithTransformer() + { + Organization ifa = new Organization("IFA"); + + using (ISession s = OpenSession()) + using (ITransaction t = s.BeginTransaction()) + { + s.Save(ifa); + t.Commit(); + } + + void AssertQuery(bool fromCache) + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + using (EnableStatisticsScope()) + { + var l = s.CreateSQLQuery("select org.ORGID as orgId, org.NAME as regionCode from ORGANIZATION org") + .AddScalar("orgId", NHibernateUtil.Int64) + .AddScalar("regionCode", NHibernateUtil.String) + .SetResultTransformer(Transformers.AliasToBean()) + .SetCacheable(true) + .List(); + t.Commit(); + + Assert.That(l.Count, Is.EqualTo(1)); + var msg = "Results are expected from " + (fromCache ? "cache" : "DB"); + Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(fromCache ? 0 : 1), msg); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(fromCache ? 1 : 0), msg); + } + } + + AssertQuery(false); + AssertQuery(true); + } + [Test] public void ResultSetMappingDefinition() { From 4b1d56ba6ba3a1a0ef9dfdc7be749939d5ef1d8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <12201973+fredericdelaporte@users.noreply.github.com> Date: Sun, 23 Oct 2022 21:50:23 +0200 Subject: [PATCH 20/22] Fix a typo --- src/NHibernate/Async/Cache/StandardQueryCache.cs | 2 +- src/NHibernate/Cache/StandardQueryCache.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NHibernate/Async/Cache/StandardQueryCache.cs b/src/NHibernate/Async/Cache/StandardQueryCache.cs index 3c537f1f540..6753d5e559e 100644 --- a/src/NHibernate/Async/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Async/Cache/StandardQueryCache.cs @@ -141,7 +141,7 @@ public async Task GetAsync(QueryKey key, ICacheAssembler[] returnTypes, b return null; } - var timestamp = GetResultsMetadata(cacheable, out var _); + var timestamp = GetResultsMetadata(cacheable, out _); if (Log.IsDebugEnabled()) Log.Debug("Checking query spaces for up-to-dateness [{0}]", StringHelper.CollectionToString(spaces)); diff --git a/src/NHibernate/Cache/StandardQueryCache.cs b/src/NHibernate/Cache/StandardQueryCache.cs index ac4fe8e67b6..e0564707196 100644 --- a/src/NHibernate/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Cache/StandardQueryCache.cs @@ -173,7 +173,7 @@ public IList Get(QueryKey key, ICacheAssembler[] returnTypes, bool isNaturalKeyL return null; } - var timestamp = GetResultsMetadata(cacheable, out var _); + var timestamp = GetResultsMetadata(cacheable, out _); if (Log.IsDebugEnabled()) Log.Debug("Checking query spaces for up-to-dateness [{0}]", StringHelper.CollectionToString(spaces)); From ae9c660b31964c49b5ba235039f80549d7fd2643 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <12201973+fredericdelaporte@users.noreply.github.com> Date: Mon, 24 Oct 2022 21:14:12 +0200 Subject: [PATCH 21/22] Apply code suggestions Co-authored-by: Alex Zaytsev --- .../Async/Cache/StandardQueryCache.cs | 16 ++++++++------ src/NHibernate/Cache/StandardQueryCache.cs | 22 ++++++++++--------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/NHibernate/Async/Cache/StandardQueryCache.cs b/src/NHibernate/Async/Cache/StandardQueryCache.cs index 6753d5e559e..d1a0ea7010b 100644 --- a/src/NHibernate/Async/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Async/Cache/StandardQueryCache.cs @@ -214,8 +214,9 @@ public async Task GetManyAsync( continue; } - var timestamp = GetResultsMetadata(cacheable, out var aliases); var key = keys[i]; - if (key.ResultTransformer?.AutoDiscoverTypes == true && HasResults(cacheable)) + var timestamp = GetResultsMetadata(cacheable, out var aliases); + var key = keys[i]; + if (key.ResultTransformer?.AutoDiscoverTypes == true && !IsEmpty(cacheable)) { key.ResultTransformer.SupplyAutoDiscoveredParameters(queryParameters[i].ResultTransformer, aliases); } @@ -330,7 +331,8 @@ private static async Task> GetCacheableResultAsync( ICacheAssembler[] returnTypes, ISessionImplementor session, IList result, - long ts, string[] aliases, CancellationToken cancellationToken) + long ts, + string[] aliases, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var cacheable = @@ -360,7 +362,7 @@ private static async Task PerformBeforeAssembleAsync( IList cacheable, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - if (!HasResults(cacheable)) + if (IsEmpty(cacheable)) return; if (returnTypes.Length == 1) @@ -392,7 +394,7 @@ private async Task PerformAssembleAsync( try { var result = new List(cacheable.Count - 1); - if (!HasResults(cacheable)) + if (IsEmpty(cacheable)) return result; if (returnTypes.Length == 1) @@ -449,7 +451,7 @@ private static async Task InitializeCollectionsAsync( IList cacheResult, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - if (!HasResults(cacheResult)) + if (IsEmpty(cacheResult)) return; var collectionIndexes = new Dictionary(); @@ -491,7 +493,7 @@ private async Task GetResultFromCacheableAsync( cancellationToken.ThrowIfCancellationRequested(); Log.Debug("returning cached query results for: {0}", key); - if (!HasResults(cacheable)) + if (IsEmpty(cacheable)) return new List(); returnTypes = GetReturnTypes(key, returnTypes, cacheable); diff --git a/src/NHibernate/Cache/StandardQueryCache.cs b/src/NHibernate/Cache/StandardQueryCache.cs index e0564707196..d19e8c79130 100644 --- a/src/NHibernate/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Cache/StandardQueryCache.cs @@ -249,8 +249,9 @@ public IList[] GetMany( continue; } - var timestamp = GetResultsMetadata(cacheable, out var aliases); var key = keys[i]; - if (key.ResultTransformer?.AutoDiscoverTypes == true && HasResults(cacheable)) + var timestamp = GetResultsMetadata(cacheable, out var aliases); + var key = keys[i]; + if (key.ResultTransformer?.AutoDiscoverTypes == true && !IsEmpty(cacheable)) { key.ResultTransformer.SupplyAutoDiscoveredParameters(queryParameters[i].ResultTransformer, aliases); } @@ -371,7 +372,8 @@ private static List GetCacheableResult( ICacheAssembler[] returnTypes, ISessionImplementor session, IList result, - long ts, string[] aliases) + long ts, + string[] aliases) { var cacheable = new List(result.Count + 1) @@ -407,9 +409,9 @@ private static long GetResultsMetadata(IList cacheable, out string[] aliases) return (long) metadataArray[0]; } - private static bool HasResults(IList cacheable) + private static bool IsEmpty(IList cacheable) // First element is the timestamp. - => cacheable.Count > 1; + => cacheable.Count <= 1; private static IEnumerable GetResultsEnumerable(IList cacheable) { @@ -425,7 +427,7 @@ private static ICacheAssembler[] GetReturnTypes( ICacheAssembler[] returnTypes, IList cacheable) { - var hasResults = HasResults(cacheable); + var hasResults = !IsEmpty(cacheable); if (key.ResultTransformer?.AutoDiscoverTypes == true && hasResults) { returnTypes = GuessTypes(cacheable); @@ -444,7 +446,7 @@ private static void PerformBeforeAssemble( ISessionImplementor session, IList cacheable) { - if (!HasResults(cacheable)) + if (IsEmpty(cacheable)) return; if (returnTypes.Length == 1) @@ -475,7 +477,7 @@ private IList PerformAssemble( try { var result = new List(cacheable.Count - 1); - if (!HasResults(cacheable)) + if (IsEmpty(cacheable)) return result; if (returnTypes.Length == 1) @@ -531,7 +533,7 @@ private static void InitializeCollections( IList assembleResult, IList cacheResult) { - if (!HasResults(cacheResult)) + if (IsEmpty(cacheResult)) return; var collectionIndexes = new Dictionary(); @@ -572,7 +574,7 @@ private IList GetResultFromCacheable( { Log.Debug("returning cached query results for: {0}", key); - if (!HasResults(cacheable)) + if (IsEmpty(cacheable)) return new List(); returnTypes = GetReturnTypes(key, returnTypes, cacheable); From 50078f0d59ecd9bb310222a88c0648d5dcb57401 Mon Sep 17 00:00:00 2001 From: Alex Zaytsev Date: Thu, 27 Oct 2022 23:42:04 +1300 Subject: [PATCH 22/22] Simplify code. Fix comment. Cleanup whitespaces --- src/NHibernate/Cache/StandardQueryCache.cs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/NHibernate/Cache/StandardQueryCache.cs b/src/NHibernate/Cache/StandardQueryCache.cs index d19e8c79130..2367a0fa2f5 100644 --- a/src/NHibernate/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Cache/StandardQueryCache.cs @@ -410,7 +410,7 @@ private static long GetResultsMetadata(IList cacheable, out string[] aliases) } private static bool IsEmpty(IList cacheable) - // First element is the timestamp. + // First element is metadata. => cacheable.Count <= 1; private static IEnumerable GetResultsEnumerable(IList cacheable) @@ -427,18 +427,13 @@ private static ICacheAssembler[] GetReturnTypes( ICacheAssembler[] returnTypes, IList cacheable) { - var hasResults = !IsEmpty(cacheable); - if (key.ResultTransformer?.AutoDiscoverTypes == true && hasResults) - { - returnTypes = GuessTypes(cacheable); - } + if (IsEmpty(cacheable)) + return returnTypes; - if (hasResults && returnTypes == null) - { - throw new HibernateException("Return types for non empty query results are null, cannot assemble the results"); - } + if (key.ResultTransformer?.AutoDiscoverTypes == true) + returnTypes = GuessTypes(cacheable); - return returnTypes; + return returnTypes ?? throw new HibernateException("Return types for non empty query results are null, cannot assemble the results"); } private static void PerformBeforeAssemble( @@ -621,10 +616,12 @@ private static ICacheAssembler[] GuessTypes(IList cacheable) foundTypes++; } } + if (foundTypes == colCount) break; } } + // If a column value was null for all rows, its type is still null: put a type which will just yield null // on null value. for (var i = 0; i < colCount; i++) @@ -632,6 +629,7 @@ private static ICacheAssembler[] GuessTypes(IList cacheable) if (returnTypes[i] == null) returnTypes[i] = NHibernateUtil.String; } + return returnTypes; }