PageRenderTime 63ms CodeModel.GetById 32ms RepoModel.GetById 1ms app.codeStats 0ms

/SubSonic.Core/Linq/Translation/RedundantSubqueryRemover.cs

https://github.com/nathanb/SubSonic-3.0
C# | 265 lines | 214 code | 30 blank | 21 comment | 115 complexity | ef94d36be1c91173d3bd0edfb6df1e80 MD5 | raw file
  1. // Copyright (c) Microsoft Corporation. All rights reserved.
  2. // This source code is made available under the terms of the Microsoft Public License (MS-PL)
  3. //Original code created by Matt Warren: http://iqtoolkit.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=19725
  4. using System;
  5. using System.Collections;
  6. using System.Collections.Generic;
  7. using System.Collections.ObjectModel;
  8. using System.Linq;
  9. using System.Linq.Expressions;
  10. using System.Reflection;
  11. using System.Text;
  12. using SubSonic.Linq.Structure;
  13. namespace SubSonic.Linq.Translation
  14. {
  15. /// <summary>
  16. /// Removes select expressions that don't add any additional semantic value
  17. /// </summary>
  18. public class RedundantSubqueryRemover : DbExpressionVisitor
  19. {
  20. private RedundantSubqueryRemover()
  21. {
  22. }
  23. public static Expression Remove(Expression expression)
  24. {
  25. expression = new RedundantSubqueryRemover().Visit(expression);
  26. expression = SubqueryMerger.Merge(expression);
  27. return expression;
  28. }
  29. protected override Expression VisitSelect(SelectExpression select)
  30. {
  31. select = (SelectExpression)base.VisitSelect(select);
  32. // first remove all purely redundant subqueries
  33. List<SelectExpression> redundant = RedundantSubqueryGatherer.Gather(select.From);
  34. if (redundant != null)
  35. {
  36. select = SubqueryRemover.Remove(select, redundant);
  37. }
  38. return select;
  39. }
  40. protected override Expression VisitProjection(ProjectionExpression proj)
  41. {
  42. proj = (ProjectionExpression)base.VisitProjection(proj);
  43. if (proj.Source.From is SelectExpression)
  44. {
  45. List<SelectExpression> redundant = RedundantSubqueryGatherer.Gather(proj.Source);
  46. if (redundant != null)
  47. {
  48. proj = SubqueryRemover.Remove(proj, redundant);
  49. }
  50. }
  51. return proj;
  52. }
  53. internal static bool IsSimpleProjection(SelectExpression select)
  54. {
  55. foreach (ColumnDeclaration decl in select.Columns)
  56. {
  57. ColumnExpression col = decl.Expression as ColumnExpression;
  58. if (col == null || decl.Name != col.Name)
  59. {
  60. return false;
  61. }
  62. }
  63. return true;
  64. }
  65. internal static bool IsNameMapProjection(SelectExpression select)
  66. {
  67. if (select.From is TableExpression) return false;
  68. SelectExpression fromSelect = select.From as SelectExpression;
  69. if (fromSelect == null || select.Columns.Count != fromSelect.Columns.Count)
  70. return false;
  71. ReadOnlyCollection<ColumnDeclaration> fromColumns = fromSelect.Columns;
  72. // test that all columns in 'select' are refering to columns in the same position
  73. // in from.
  74. for (int i = 0, n = select.Columns.Count; i < n; i++)
  75. {
  76. ColumnExpression col = select.Columns[i].Expression as ColumnExpression;
  77. if (col == null || !(col.Name == fromColumns[i].Name))
  78. return false;
  79. }
  80. return true;
  81. }
  82. internal static bool IsInitialProjection(SelectExpression select)
  83. {
  84. return select.From is TableExpression;
  85. }
  86. class RedundantSubqueryGatherer : DbExpressionVisitor
  87. {
  88. List<SelectExpression> redundant;
  89. private RedundantSubqueryGatherer()
  90. {
  91. }
  92. internal static List<SelectExpression> Gather(Expression source)
  93. {
  94. RedundantSubqueryGatherer gatherer = new RedundantSubqueryGatherer();
  95. gatherer.Visit(source);
  96. return gatherer.redundant;
  97. }
  98. private static bool IsRedudantSubquery(SelectExpression select)
  99. {
  100. return (IsSimpleProjection(select) || IsNameMapProjection(select))
  101. && !select.IsDistinct
  102. && select.Take == null
  103. && select.Skip == null
  104. && select.Where == null
  105. && (select.OrderBy == null || select.OrderBy.Count == 0)
  106. && (select.GroupBy == null || select.GroupBy.Count == 0);
  107. }
  108. protected override Expression VisitSelect(SelectExpression select)
  109. {
  110. if (IsRedudantSubquery(select))
  111. {
  112. if (this.redundant == null)
  113. {
  114. this.redundant = new List<SelectExpression>();
  115. }
  116. this.redundant.Add(select);
  117. }
  118. return select;
  119. }
  120. protected override Expression VisitSubquery(SubqueryExpression subquery)
  121. {
  122. // don't gather inside scalar and exists
  123. return subquery;
  124. }
  125. }
  126. class SubqueryMerger : DbExpressionVisitor
  127. {
  128. private SubqueryMerger()
  129. {
  130. }
  131. internal static Expression Merge(Expression expression)
  132. {
  133. return new SubqueryMerger().Visit(expression);
  134. }
  135. bool isTopLevel = true;
  136. protected override Expression VisitSelect(SelectExpression select)
  137. {
  138. bool wasTopLevel = isTopLevel;
  139. isTopLevel = false;
  140. select = (SelectExpression)base.VisitSelect(select);
  141. // next attempt to merge subqueries that would have been removed by the above
  142. // logic except for the existence of a where clause
  143. while (CanMergeWithFrom(select, wasTopLevel))
  144. {
  145. SelectExpression fromSelect = GetLeftMostSelect(select.From);
  146. // remove the redundant subquery
  147. select = SubqueryRemover.Remove(select, fromSelect);
  148. // merge where expressions
  149. Expression where = select.Where;
  150. if (fromSelect.Where != null)
  151. {
  152. if (where != null)
  153. {
  154. where = Expression.And(fromSelect.Where, where);
  155. }
  156. else
  157. {
  158. where = fromSelect.Where;
  159. }
  160. }
  161. var orderBy = select.OrderBy != null && select.OrderBy.Count > 0 ? select.OrderBy : fromSelect.OrderBy;
  162. var groupBy = select.GroupBy != null && select.GroupBy.Count > 0 ? select.GroupBy : fromSelect.GroupBy;
  163. Expression skip = select.Skip != null ? select.Skip : fromSelect.Skip;
  164. Expression take = select.Take != null ? select.Take : fromSelect.Take;
  165. bool isDistinct = select.IsDistinct | fromSelect.IsDistinct;
  166. if (where != select.Where
  167. || orderBy != select.OrderBy
  168. || groupBy != select.GroupBy
  169. || isDistinct != select.IsDistinct
  170. || skip != select.Skip
  171. || take != select.Take)
  172. {
  173. select = new SelectExpression(select.Alias, select.Columns, select.From, where, orderBy, groupBy, isDistinct, skip, take);
  174. }
  175. }
  176. return select;
  177. }
  178. private static SelectExpression GetLeftMostSelect(Expression source)
  179. {
  180. SelectExpression select = source as SelectExpression;
  181. if (select != null) return select;
  182. JoinExpression join = source as JoinExpression;
  183. if (join != null) return GetLeftMostSelect(join.Left);
  184. return null;
  185. }
  186. private static bool IsColumnProjection(SelectExpression select)
  187. {
  188. for (int i = 0, n = select.Columns.Count; i < n; i++)
  189. {
  190. var cd = select.Columns[i];
  191. if (cd.Expression.NodeType != (ExpressionType)DbExpressionType.Column &&
  192. cd.Expression.NodeType != ExpressionType.Constant)
  193. return false;
  194. }
  195. return true;
  196. }
  197. private static bool CanMergeWithFrom(SelectExpression select, bool isTopLevel)
  198. {
  199. SelectExpression fromSelect = GetLeftMostSelect(select.From);
  200. if (fromSelect == null)
  201. return false;
  202. if (!IsColumnProjection(fromSelect))
  203. return false;
  204. bool selHasNameMapProjection = IsNameMapProjection(select);
  205. bool selHasOrderBy = select.OrderBy != null && select.OrderBy.Count > 0;
  206. bool selHasGroupBy = select.GroupBy != null && select.GroupBy.Count > 0;
  207. bool selHasAggregates = AggregateChecker.HasAggregates(select);
  208. bool frmHasOrderBy = fromSelect.OrderBy != null && fromSelect.OrderBy.Count > 0;
  209. bool frmHasGroupBy = fromSelect.GroupBy != null && fromSelect.GroupBy.Count > 0;
  210. // both cannot have orderby
  211. if (selHasOrderBy && frmHasOrderBy)
  212. return false;
  213. // both cannot have groupby
  214. if (selHasOrderBy && frmHasOrderBy)
  215. return false;
  216. // cannot move forward order-by if outer has group-by
  217. if (frmHasOrderBy && (selHasGroupBy || selHasAggregates || select.IsDistinct))
  218. return false;
  219. // cannot move forward group-by if outer has where clause
  220. if (frmHasGroupBy /*&& (select.Where != null)*/) // need to assert projection is the same in order to move group-by forward
  221. return false;
  222. // cannot move forward a take if outer has take or skip or distinct
  223. if (fromSelect.Take != null && (select.Take != null || select.Skip != null || select.IsDistinct || selHasAggregates || selHasGroupBy))
  224. return false;
  225. // cannot move forward a skip if outer has skip or distinct
  226. if (fromSelect.Skip != null && (select.Skip != null || select.IsDistinct || selHasAggregates || selHasGroupBy))
  227. return false;
  228. // cannot move forward a distinct if outer has take, skip, groupby or a different projection
  229. if (fromSelect.IsDistinct && (select.Take != null || select.Skip != null || !selHasNameMapProjection || selHasGroupBy || selHasAggregates || (selHasOrderBy && !isTopLevel)))
  230. return false;
  231. return true;
  232. }
  233. }
  234. }
  235. }