PageRenderTime 53ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 1ms

/mcs/class/referencesource/System.Data.Entity/System/Data/Query/PlanCompiler/SubqueryTrackingVisitor.cs

https://github.com/pruiz/mono
C# | 287 lines | 146 code | 34 blank | 107 comment | 22 complexity | dc983c8da00b2d912c38e613975a92c4 MD5 | raw file
Possible License(s): LGPL-2.0, MPL-2.0-no-copyleft-exception, CC-BY-SA-3.0, GPL-2.0
  1. //---------------------------------------------------------------------
  2. // <copyright file="SubqueryTrackingVisitor.cs" company="Microsoft">
  3. // Copyright (c) Microsoft Corporation. All rights reserved.
  4. // </copyright>
  5. //
  6. // @owner Microsoft
  7. // @backupOwner Microsoft
  8. //---------------------------------------------------------------------
  9. using System;
  10. using System.Collections.Generic;
  11. using System.Data.Query.InternalTrees;
  12. //using System.Diagnostics; // Please use PlanCompiler.Assert instead of Debug.Assert in this class...
  13. // It is fine to use Debug.Assert in cases where you assert an obvious thing that is supposed
  14. // to prevent from simple mistakes during development (e.g. method argument validation
  15. // in cases where it was you who created the variables or the variables had already been validated or
  16. // in "else" clauses where due to code changes (e.g. adding a new value to an enum type) the default
  17. // "else" block is chosen why the new condition should be treated separately). This kind of asserts are
  18. // (can be) helpful when developing new code to avoid simple mistakes but have no or little value in
  19. // the shipped product.
  20. // PlanCompiler.Assert *MUST* be used to verify conditions in the trees. These would be assumptions
  21. // about how the tree was built etc. - in these cases we probably want to throw an exception (this is
  22. // what PlanCompiler.Assert does when the condition is not met) if either the assumption is not correct
  23. // or the tree was built/rewritten not the way we thought it was.
  24. // Use your judgment - if you rather remove an assert than ship it use Debug.Assert otherwise use
  25. // PlanCompiler.Assert.
  26. namespace System.Data.Query.PlanCompiler
  27. {
  28. /// <summary>
  29. /// The SubqueryTracking Visitor serves as a base class for the visitors that may turn
  30. /// scalar subqueryies into outer-apply subqueries.
  31. /// </summary>
  32. internal abstract class SubqueryTrackingVisitor : BasicOpVisitorOfNode
  33. {
  34. #region Private State
  35. protected readonly PlanCompiler m_compilerState;
  36. protected Command m_command { get { return m_compilerState.Command; } }
  37. // nested subquery tracking
  38. protected readonly Stack<Node> m_ancestors = new Stack<Node>();
  39. private readonly Dictionary<Node, List<Node>> m_nodeSubqueries = new Dictionary<Node, List<Node>>();
  40. #endregion
  41. #region Constructor
  42. protected SubqueryTrackingVisitor(PlanCompiler planCompilerState)
  43. {
  44. this.m_compilerState = planCompilerState;
  45. }
  46. #endregion
  47. #region Subquery Handling
  48. /// <summary>
  49. /// Adds a subquery to the list of subqueries for the relOpNode
  50. /// </summary>
  51. /// <param name="relOpNode">the RelOp node</param>
  52. /// <param name="subquery">the subquery</param>
  53. protected void AddSubqueryToRelOpNode(Node relOpNode, Node subquery)
  54. {
  55. List<Node> nestedSubqueries;
  56. // Create an entry in the map if there isn't one already
  57. if (!m_nodeSubqueries.TryGetValue(relOpNode, out nestedSubqueries))
  58. {
  59. nestedSubqueries = new List<Node>();
  60. m_nodeSubqueries[relOpNode] = nestedSubqueries;
  61. }
  62. // add this subquery to the list of currently tracked subqueries
  63. nestedSubqueries.Add(subquery);
  64. }
  65. /// <summary>
  66. /// Add a subquery to the "parent" relop node
  67. /// </summary>
  68. /// <param name="outputVar">the output var to be used - at the current location - in lieu of the subquery</param>
  69. /// <param name="subquery">the subquery to move</param>
  70. /// <returns>a var ref node for the var returned from the subquery</returns>
  71. protected Node AddSubqueryToParentRelOp(Var outputVar, Node subquery)
  72. {
  73. Node ancestor = FindRelOpAncestor();
  74. PlanCompiler.Assert(ancestor != null, "no ancestors found?");
  75. AddSubqueryToRelOpNode(ancestor, subquery);
  76. subquery = m_command.CreateNode(m_command.CreateVarRefOp(outputVar));
  77. return subquery;
  78. }
  79. /// <summary>
  80. /// Find the first RelOp node that is in my ancestral path.
  81. /// If I see a PhysicalOp, then I don't have a RelOp parent
  82. /// </summary>
  83. /// <returns>the first RelOp node</returns>
  84. protected Node FindRelOpAncestor()
  85. {
  86. foreach (Node n in m_ancestors)
  87. {
  88. if (n.Op.IsRelOp)
  89. {
  90. return n;
  91. }
  92. else if (n.Op.IsPhysicalOp)
  93. {
  94. return null;
  95. }
  96. }
  97. return null;
  98. }
  99. #endregion
  100. #region Visitor Helpers
  101. /// <summary>
  102. /// Extends the base class implementation of VisitChildren.
  103. /// Wraps the call to visitchildren() by first adding the current node
  104. /// to the stack of "ancestors", and then popping back the node at the end
  105. /// </summary>
  106. /// <param name="n">Current node</param>
  107. protected override void VisitChildren(Node n)
  108. {
  109. // Push the current node onto the stack
  110. m_ancestors.Push(n);
  111. for (int i = 0; i < n.Children.Count; i++)
  112. {
  113. n.Children[i] = VisitNode(n.Children[i]);
  114. }
  115. m_ancestors.Pop();
  116. }
  117. #endregion
  118. #region Visitor Methods
  119. #region RelOps
  120. /// <summary>
  121. /// Augments a node with a number of OuterApply's - one for each subquery
  122. /// If S1, S2, ... are the list of subqueries for the node, and D is the
  123. /// original (driver) input, we convert D into
  124. /// OuterApply(OuterApply(D, S1), S2), ...
  125. /// </summary>
  126. /// <param name="input">the input (driver) node</param>
  127. /// <param name="subqueries">List of subqueries</param>
  128. /// <param name="inputFirst">should the input node be first in the apply chain, or the last?</param>
  129. /// <returns>The resulting node tree</returns>
  130. private Node AugmentWithSubqueries(Node input, List<Node> subqueries, bool inputFirst)
  131. {
  132. Node newNode;
  133. int subqueriesStartPos;
  134. if (inputFirst)
  135. {
  136. newNode = input;
  137. subqueriesStartPos = 0;
  138. }
  139. else
  140. {
  141. newNode = subqueries[0];
  142. subqueriesStartPos = 1;
  143. }
  144. for (int i = subqueriesStartPos; i < subqueries.Count; i++)
  145. {
  146. OuterApplyOp op = m_command.CreateOuterApplyOp();
  147. newNode = m_command.CreateNode(op, newNode, subqueries[i]);
  148. }
  149. if (!inputFirst)
  150. {
  151. // The driver node uses a cross apply to ensure that no results are produced
  152. // for an empty driver.
  153. newNode = m_command.CreateNode(m_command.CreateCrossApplyOp(), newNode, input);
  154. }
  155. // We may need to perform join elimination
  156. m_compilerState.MarkPhaseAsNeeded(PlanCompilerPhase.JoinElimination);
  157. return newNode;
  158. }
  159. /// <summary>
  160. /// Default processing for RelOps.
  161. /// - First, we mark the current node as its own ancestor (so that any
  162. /// subqueries that we detect internally will be added to this node's list)
  163. /// - then, visit each child
  164. /// - finally, accumulate all nested subqueries.
  165. /// - if the current RelOp has only one input, then add the nested subqueries via
  166. /// Outer apply nodes to this input.
  167. ///
  168. /// The interesting RelOps are
  169. /// Project, Filter, GroupBy, Sort,
  170. /// Should we break this out into separate functions instead?
  171. /// </summary>
  172. /// <param name="op">Current RelOp</param>
  173. /// <param name="n">Node to process</param>
  174. /// <returns>Current subtree</returns>
  175. protected override Node VisitRelOpDefault(RelOp op, Node n)
  176. {
  177. VisitChildren(n); // visit all my children first
  178. // Then identify all the subqueries that have shown up as part of my node
  179. // Create Apply Nodes for each of these.
  180. List<Node> nestedSubqueries;
  181. if (m_nodeSubqueries.TryGetValue(n, out nestedSubqueries) && nestedSubqueries.Count > 0)
  182. {
  183. // Validate - this must only apply to the following nodes
  184. PlanCompiler.Assert(
  185. n.Op.OpType == OpType.Project || n.Op.OpType == OpType.Filter ||
  186. n.Op.OpType == OpType.GroupBy || n.Op.OpType == OpType.GroupByInto,
  187. "VisitRelOpDefault: Unexpected op?" + n.Op.OpType);
  188. Node newInputNode = AugmentWithSubqueries(n.Child0, nestedSubqueries, true);
  189. // Now make this the new input child
  190. n.Child0 = newInputNode;
  191. }
  192. return n;
  193. }
  194. /// <summary>
  195. /// Processing for all JoinOps
  196. /// </summary>
  197. /// <param name="op">JoinOp</param>
  198. /// <param name="n">Current subtree</param>
  199. /// <returns>Whether the node was modified</returns>
  200. protected bool ProcessJoinOp(JoinBaseOp op, Node n)
  201. {
  202. VisitChildren(n); // visit all my children first
  203. // then check to see if we have any nested subqueries. This can only
  204. // occur in the join condition.
  205. // What we'll do in this case is to convert the join condition - "p" into
  206. // p -> Exists(Filter(SingleRowTableOp, p))
  207. // We will then move the subqueries into an outerApply on the SingleRowTable
  208. List<Node> nestedSubqueries;
  209. if (!m_nodeSubqueries.TryGetValue(n, out nestedSubqueries))
  210. {
  211. return false;
  212. }
  213. PlanCompiler.Assert(n.Op.OpType == OpType.InnerJoin ||
  214. n.Op.OpType == OpType.LeftOuterJoin ||
  215. n.Op.OpType == OpType.FullOuterJoin, "unexpected op?");
  216. PlanCompiler.Assert(n.HasChild2, "missing second child to JoinOp?");
  217. Node joinCondition = n.Child2;
  218. Node inputNode = m_command.CreateNode(m_command.CreateSingleRowTableOp());
  219. inputNode = AugmentWithSubqueries(inputNode, nestedSubqueries, true);
  220. Node filterNode = m_command.CreateNode(m_command.CreateFilterOp(), inputNode, joinCondition);
  221. Node existsNode = m_command.CreateNode(m_command.CreateExistsOp(), filterNode);
  222. n.Child2 = existsNode;
  223. return true;
  224. }
  225. /// <summary>
  226. /// Visitor for UnnestOp. If the child has any subqueries, we need to convert this
  227. /// into an
  228. /// OuterApply(S, Unnest)
  229. /// unlike the other cases where the OuterApply will appear as the input of the node
  230. /// </summary>
  231. /// <param name="op">the unnestOp</param>
  232. /// <param name="n">current subtree</param>
  233. /// <returns>modified subtree</returns>
  234. public override Node Visit(UnnestOp op, Node n)
  235. {
  236. VisitChildren(n); // visit all my children first
  237. List<Node> nestedSubqueries;
  238. if (m_nodeSubqueries.TryGetValue(n, out nestedSubqueries))
  239. {
  240. // We pass 'inputFirst = false' since the subqueries contribute to the driver in the unnest,
  241. // they are not generated by the unnest.
  242. Node newNode = AugmentWithSubqueries(n, nestedSubqueries, false /* inputFirst */);
  243. return newNode;
  244. }
  245. else
  246. {
  247. return n;
  248. }
  249. }
  250. #endregion
  251. #endregion
  252. }
  253. }