context: using System;
  using System.Collections;
  using System.Collections.Generic;
  using System.Linq;
  using NHibernate.Cfg;
  using NHibernate.Collection;
  using NHibernate.Engine;
  using NHibernate.Persister.Collection;
  using NHibernate.Type;
  using NHibernate.Util;
  
  namespace NHibernate.Cache
  {
  	/// <summary>
  	/// The standard implementation of the Hibernate <see cref="IQueryCache" />
  	/// interface.  This implementation is very good at recognizing stale query
  	/// results and re-running queries when it detects this condition, recaching
  	/// the new results.
  	/// </summary>
  	public partial class StandardQueryCache : IQueryCache, IBatchableQueryCache
  	{
  		private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof (StandardQueryCache));
  		private readonly string _regionName;
  		private readonly UpdateTimestampsCache _updateTimestampsCache;
  		private readonly CacheBase _cache;
  
  		// Since v5.3
  		[Obsolete("Please use overload with a CacheBase parameter.")]
  		public StandardQueryCache(
  			Settings settings,
  			IDictionary<string, string> props,
  			UpdateTimestampsCache updateTimestampsCache,
  			string regionName)
  			: this(
  				updateTimestampsCache,
  				CacheFactory.BuildCacheBase(
  					settings.GetFullCacheRegionName(regionName ?? typeof(StandardQueryCache).FullName),
  					settings,
  					props))
  		{
  		}
  
  		/// <summary>
  		/// Build a query cache.
  		/// </summary>
  		/// <param name="updateTimestampsCache">The cache of updates timestamps.</param>
  		/// <param name="regionCache">The <see cref="ICache" /> to use for the region.</param>
  		public StandardQueryCache(
  			UpdateTimestampsCache updateTimestampsCache,
  			CacheBase regionCache)
  		{
  			if (regionCache == null)
  				throw new ArgumentNullException(nameof(regionCache));
  
  			_regionName = regionCache.RegionName;
  			Log.Info("starting query cache at region: {0}", _regionName);
  
  			_cache = regionCache;
  			_updateTimestampsCache = updateTimestampsCache;
  		}
  
  		#region IQueryCache Members
  
  		// 6.0 TODO: type as CacheBase instead
  #pragma warning disable 618
  		public ICache Cache => _cache;
  #pragma warning restore 618
  
  		public string RegionName
  		{
  			get { return _regionName; }
  		}
  
  		public void Clear()
  		{
  			Cache.Clear();
  		}
  
  		/// <inheritdoc />
  		public bool Put(
  			QueryKey key,
  			QueryParameters queryParameters,
  			ICacheAssembler[] returnTypes,
  			IList result,
  			ISessionImplementor session)
  		{
  			// 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
  		[Obsolete]
  		public bool Put(QueryKey key, ICacheAssembler[] returnTypes, IList result, bool isNaturalKeyLookup, ISessionImplementor session)
  		{
  			if (isNaturalKeyLookup && 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));
  
  			return true;
  		}
  
  		/// <inheritdoc />
  		public IList Get(
  			QueryKey key,
  			QueryParameters queryParameters,
  			ICacheAssembler[] returnTypes,
  			ISet<string> spaces,
  			ISessionImplementor session)
  		{
  			var persistenceContext = session.PersistenceContext;
  			var defaultReadOnlyOrig = persistenceContext.DefaultReadOnly;
  
  			if (queryParameters.IsReadOnlyInitialized)
  				persistenceContext.DefaultReadOnly = queryParameters.ReadOnly;
  			else
  				queryParameters.ReadOnly = persistenceContext.DefaultReadOnly;
  
  			try
  			{
  				// 6.0 TODO: inline the call.
  #pragma warning disable 612
  				return Get(key, returnTypes, queryParameters.NaturalKeyLookup, spaces, session);
  #pragma warning restore 612
  			}
  			finally
  			{
  				persistenceContext.DefaultReadOnly = defaultReadOnlyOrig;
  			}
  		}
  
  		// Since 5.2
  		[Obsolete]
  		public IList Get(QueryKey key, ICacheAssembler[] returnTypes, bool isNaturalKeyLookup, ISet<string> spaces, ISessionImplementor session)
  		{
  			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 (!isNaturalKeyLookup && !IsUpToDate(spaces, timestamp))
  			{
  				Log.Debug("cached query results were not up to date for: {0}", key);
  				return null;
  			}
  
  			return GetResultFromCacheable(key, returnTypes, isNaturalKeyLookup, session, cacheable);
  		}
  
  		/// <inheritdoc />
  		public bool[] PutMany(
  			QueryKey[] keys,
  			QueryParameters[] queryParameters,
  			ICacheAssembler[][] returnTypes,
  			IList[] results,
  			ISessionImplementor session)
  		{
  			if (Log.IsDebugEnabled())
  				Log.Debug("caching query results in region: '{0}'; {1}", _regionName, StringHelper.CollectionToString(keys));
  
  			var cached = new bool[keys.Length];
  			var ts = session.Factory.Settings.CacheProvider.NextTimestamp();
  			var cachedKeys = new List<object>();
  			var cachedResults = new List<object>();
  			for (var i = 0; i < keys.Length; i++)
  			{
  				var result = results[i];
  				if (queryParameters[i].NaturalKeyLookup && result.Count == 0)
  					continue;
  
  				cached[i] = true;
  				cachedKeys.Add(keys[i]);
  				cachedResults.Add(GetCacheableResult(returnTypes[i], session, result, ts));
  			}
  
  			_cache.PutMany(cachedKeys.ToArray(), cachedResults.ToArray());
  
  			return cached;
  		}
  
  		/// <inheritdoc />
  		public IList[] GetMany(
  			QueryKey[] keys,
  			QueryParameters[] queryParameters,
  			ICacheAssembler[][] returnTypes,
  			ISet<string>[] spaces,
  			ISessionImplementor session)
  		{
  			if (Log.IsDebugEnabled())
  				Log.Debug("checking cached query results in region: '{0}'; {1}", _regionName, StringHelper.CollectionToString(keys));
  
  			var cacheables = _cache.GetMany(keys);
  
  			var spacesToCheck = new List<ISet<string>>();
  			var checkedSpacesIndexes = new HashSet<int>();
  			var checkedSpacesTimestamp = new List<long>();
  			for (var i = 0; i < keys.Length; i++)
  			{
  				var cacheable = (IList) cacheables[i];
  				if (cacheable == null)
  				{
  					Log.Debug("query results were not found in cache: {0}", keys[i]);
  					continue;
  				}
  
  				var querySpaces = spaces[i];
  				if (queryParameters[i].NaturalKeyLookup || querySpaces.Count == 0)
  					continue;
  
  				spacesToCheck.Add(querySpaces);
  				checkedSpacesIndexes.Add(i);
  				// The timestamp is the first element of the cache result.
  				checkedSpacesTimestamp.Add((long) cacheable[0]);
  				if (Log.IsDebugEnabled())
  					Log.Debug("Checking query spaces for up-to-dateness [{0}]", StringHelper.CollectionToString(querySpaces));
  			}
  
  			var upToDates = spacesToCheck.Count > 0
  				? _updateTimestampsCache.AreUpToDate(spacesToCheck.ToArray(), checkedSpacesTimestamp.ToArray())
  				: Array.Empty<bool>();
  
  			var upToDatesIndex = 0;
  			var persistenceContext = session.PersistenceContext;
  			var defaultReadOnlyOrig = persistenceContext.DefaultReadOnly;
  			var results = new IList[keys.Length];
  			var finalReturnTypes = new ICacheAssembler[keys.Length][];
  			try
  			{
  				session.PersistenceContext.BatchFetchQueue.InitializeQueryCacheQueue();
  
  				for (var i = 0; i < keys.Length; i++)
  				{
  					var cacheable = (IList) cacheables[i];
  
gt: 					if (cacheable == null)