PageRenderTime 44ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/Main/Core/Repositories/AggregateQuery.cs

https://bitbucket.org/EvilDev/evildev.nugetserver
C# | 317 lines | 244 code | 47 blank | 26 comment | 16 complexity | 37d2aeae0ddb14ffe1804ec4f4e6081c MD5 | raw file
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Diagnostics.CodeAnalysis;
  5. using System.Linq;
  6. using System.Linq.Expressions;
  7. using System.Reflection;
  8. using System.Threading.Tasks;
  9. namespace NuGet
  10. {
  11. internal class AggregateQuery<TVal> : IQueryable<TVal>, IQueryProvider, IOrderedQueryable<TVal>
  12. {
  13. private const int QueryCacheSize = 30;
  14. private readonly IEnumerable<IQueryable<TVal>> _queryables;
  15. private readonly Expression _expression;
  16. private readonly IEqualityComparer<TVal> _equalityComparer;
  17. private readonly IList<IEnumerable<TVal>> _subQueries;
  18. private readonly bool _ignoreFailures;
  19. private readonly ILogger _logger;
  20. public AggregateQuery(IEnumerable<IQueryable<TVal>> queryables, IEqualityComparer<TVal> equalityComparer, ILogger logger, bool ignoreFailures)
  21. {
  22. _queryables = queryables;
  23. _equalityComparer = equalityComparer;
  24. _expression = Expression.Constant(this);
  25. _ignoreFailures = ignoreFailures;
  26. _logger = logger;
  27. _subQueries = GetSubQueries(_expression);
  28. }
  29. /// <summary>
  30. /// This constructor is used by unit tests.
  31. /// </summary>
  32. private AggregateQuery(IEnumerable<IQueryable<TVal>> queryables,
  33. IEqualityComparer<TVal> equalityComparer,
  34. IList<IEnumerable<TVal>> subQueries,
  35. Expression expression,
  36. ILogger logger,
  37. bool ignoreInvalidRepositories)
  38. {
  39. _queryables = queryables;
  40. _equalityComparer = equalityComparer;
  41. _expression = expression;
  42. _subQueries = subQueries;
  43. _ignoreFailures = ignoreInvalidRepositories;
  44. _logger = logger;
  45. }
  46. public IEnumerator<TVal> GetEnumerator()
  47. {
  48. // Rewrite the expression for aggregation i.e. remove things that don't make sense to apply
  49. // after all initial expression has been applied.
  50. var aggregateQuery = GetAggregateEnumerable().AsQueryable();
  51. Expression aggregateExpression = RewriteForAggregation(aggregateQuery, Expression);
  52. return aggregateQuery.Provider.CreateQuery<TVal>(aggregateExpression).GetEnumerator();
  53. }
  54. IEnumerator IEnumerable.GetEnumerator()
  55. {
  56. return GetEnumerator();
  57. }
  58. public Type ElementType
  59. {
  60. get
  61. {
  62. return typeof(TVal);
  63. }
  64. }
  65. public Expression Expression
  66. {
  67. get
  68. {
  69. return _expression;
  70. }
  71. }
  72. public IQueryProvider Provider
  73. {
  74. get
  75. {
  76. return this;
  77. }
  78. }
  79. public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
  80. {
  81. return (IQueryable<TElement>)CreateQuery(typeof(TElement), expression);
  82. }
  83. public IQueryable CreateQuery(Expression expression)
  84. {
  85. // Copied logic from EnumerableQuery
  86. if (expression == null)
  87. {
  88. throw new ArgumentNullException("expression");
  89. }
  90. Type elementType = QueryableUtility.FindGenericType(typeof(IQueryable<>), expression.Type);
  91. if (elementType == null)
  92. {
  93. throw new ArgumentException(String.Empty, "expression");
  94. }
  95. return CreateQuery(elementType, expression);
  96. }
  97. public TResult Execute<TResult>(Expression expression)
  98. {
  99. var results = (from queryable in _queryables
  100. select TryExecute<TResult>(queryable, expression)).AsQueryable();
  101. if (QueryableUtility.IsQueryableMethod(expression, "Count"))
  102. {
  103. // HACK: This is in correct since we aren't removing duplicates but count is mostly for paging
  104. // so we don't care *that* much
  105. return (TResult)(object)results.Cast<int>().Sum();
  106. }
  107. return TryExecute<TResult>(results, expression);
  108. }
  109. public object Execute(Expression expression)
  110. {
  111. return Execute<object>(expression);
  112. }
  113. private IEnumerable<TVal> GetAggregateEnumerable()
  114. {
  115. // Used to pick the right element from each sub query in the right order
  116. var comparer = new OrderingComparer<TVal>(Expression);
  117. if (!comparer.CanCompare)
  118. {
  119. // If the original queries do not have sort expressions, we'll use the order of the subqueries to read results out.
  120. return _subQueries.SelectMany(query => _ignoreFailures ? query.SafeIterate() : query)
  121. .Distinct(_equalityComparer);
  122. }
  123. return ReadOrderedQueues(comparer);
  124. }
  125. /// <summary>
  126. /// Reads the minimal set of queries
  127. /// </summary>
  128. /// <param name="comparer"></param>
  129. /// <returns></returns>
  130. private IEnumerable<TVal> ReadOrderedQueues(IComparer<TVal> comparer)
  131. {
  132. // Create lazy queues over each sub query so we can lazily pull items from it
  133. var lazyQueues = _subQueries.Select(query => new LazyQueue<TVal>(query.GetEnumerator())).ToList();
  134. // Used to keep track of everything we've seen so far (we never show duplicates)
  135. var seen = new HashSet<TVal>(_equalityComparer);
  136. do
  137. {
  138. TVal minElement = default(TVal);
  139. LazyQueue<TVal> minQueue = null;
  140. // Run tasks in parallel
  141. var tasks = (from queue in lazyQueues
  142. select Task.Factory.StartNew<TaskResult>(() => ReadQueue(queue))
  143. ).ToArray();
  144. // Wait for everything to complete
  145. Task.WaitAll(tasks);
  146. foreach (var task in tasks)
  147. {
  148. if (task.Result.HasValue)
  149. {
  150. // Keep track of the minimum element in the list
  151. if (minElement == null || comparer.Compare(task.Result.Value, minElement) < 0)
  152. {
  153. minElement = task.Result.Value;
  154. minQueue = task.Result.Queue;
  155. }
  156. }
  157. else
  158. {
  159. // Remove the enumerator if it's empty
  160. lazyQueues.Remove(task.Result.Queue);
  161. }
  162. }
  163. if (lazyQueues.Any())
  164. {
  165. if (seen.Add(minElement))
  166. {
  167. yield return minElement;
  168. }
  169. // Clear the top of the enumerator we just peeked
  170. minQueue.Dequeue();
  171. }
  172. } while (lazyQueues.Count > 0);
  173. }
  174. [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "By definition we want to suppress all exceptions if the flag is set")]
  175. private TaskResult ReadQueue(LazyQueue<TVal> queue)
  176. {
  177. var result = new TaskResult { Queue = queue };
  178. TVal current;
  179. if (_ignoreFailures)
  180. {
  181. try
  182. {
  183. result.HasValue = queue.TryPeek(out current);
  184. }
  185. catch (Exception ex)
  186. {
  187. LogWarning(ex);
  188. current = default(TVal);
  189. }
  190. }
  191. else
  192. {
  193. result.HasValue = queue.TryPeek(out current);
  194. }
  195. result.Value = current;
  196. return result;
  197. }
  198. private IList<IEnumerable<TVal>> GetSubQueries(Expression expression)
  199. {
  200. return _queryables.Select(query => GetSubQuery(query, expression)).ToList();
  201. }
  202. private IQueryable CreateQuery(Type elementType, Expression expression)
  203. {
  204. var queryType = typeof(AggregateQuery<>).MakeGenericType(elementType);
  205. var ctor = queryType.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).Single();
  206. var subQueries = _subQueries;
  207. // Only update subqueries for ordering and where clauses
  208. if (QueryableUtility.IsQueryableMethod(expression, "Where") ||
  209. QueryableUtility.IsOrderingMethod(expression))
  210. {
  211. subQueries = GetSubQueries(expression);
  212. }
  213. return (IQueryable)ctor.Invoke(new object[] { _queryables, _equalityComparer, subQueries, expression, _logger, _ignoreFailures });
  214. }
  215. private void LogWarning(Exception ex)
  216. {
  217. _logger.Log(MessageLevel.Warning, ExceptionUtility.Unwrap(ex).Message);
  218. }
  219. private static IEnumerable<TVal> GetSubQuery(IQueryable queryable, Expression expression)
  220. {
  221. expression = Rewrite(queryable, expression);
  222. IQueryable<TVal> newQuery = queryable.Provider.CreateQuery<TVal>(expression);
  223. // Create the query and only get up to the query cache size
  224. return new BufferedEnumerable<TVal>(newQuery, QueryCacheSize);
  225. }
  226. private static TResult Execute<TResult>(IQueryable queryable, Expression expression)
  227. {
  228. return queryable.Provider
  229. .Execute<TResult>(Rewrite(queryable, expression));
  230. }
  231. [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "By definition we want to suppress all exceptions if the flag is set")]
  232. private TResult TryExecute<TResult>(IQueryable queryable, Expression expression)
  233. {
  234. if (_ignoreFailures)
  235. {
  236. try
  237. {
  238. return Execute<TResult>(queryable, expression);
  239. }
  240. catch (Exception ex)
  241. {
  242. LogWarning(ex);
  243. return default(TResult);
  244. }
  245. }
  246. return Execute<TResult>(queryable, expression);
  247. }
  248. private static Expression RewriteForAggregation(IQueryable queryable, Expression expression)
  249. {
  250. // Remove filters, and ordering from the aggregate query
  251. return new ExpressionRewriter(queryable, new[] { "Where",
  252. "OrderBy",
  253. "OrderByDescending",
  254. "ThenBy",
  255. "ThenByDescending" }).Visit(expression);
  256. }
  257. private static Expression Rewrite(IQueryable queryable, Expression expression)
  258. {
  259. // Remove all take an skip and take expression from individual linq providers
  260. return new ExpressionRewriter(queryable, new[] { "Skip",
  261. "Take" }).Visit(expression);
  262. }
  263. private class TaskResult
  264. {
  265. public LazyQueue<TVal> Queue { get; set; }
  266. public bool HasValue { get; set; }
  267. public TVal Value { get; set; }
  268. }
  269. }
  270. }