1 //---------------------------------------------------------------------
2 // <copyright file="Normalizer.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
8 //---------------------------------------------------------------------
11 using System.Collections.Generic;
12 using System.Data.Common;
13 using System.Data.Metadata.Edm;
14 using System.Data.Query.InternalTrees;
15 //using System.Diagnostics; // Please use PlanCompiler.Assert instead of Debug.Assert in this class...
17 // It is fine to use Debug.Assert in cases where you assert an obvious thing that is supposed
18 // to prevent from simple mistakes during development (e.g. method argument validation
19 // in cases where it was you who created the variables or the variables had already been validated or
20 // in "else" clauses where due to code changes (e.g. adding a new value to an enum type) the default
21 // "else" block is chosen why the new condition should be treated separately). This kind of asserts are
22 // (can be) helpful when developing new code to avoid simple mistakes but have no or little value in
23 // the shipped product.
24 // PlanCompiler.Assert *MUST* be used to verify conditions in the trees. These would be assumptions
25 // about how the tree was built etc. - in these cases we probably want to throw an exception (this is
26 // what PlanCompiler.Assert does when the condition is not met) if either the assumption is not correct
27 // or the tree was built/rewritten not the way we thought it was.
28 // Use your judgment - if you rather remove an assert than ship it use Debug.Assert otherwise use
29 // PlanCompiler.Assert.
32 // The normalizer performs transformations of the tree to bring it to a 'normalized' format
33 // In particular it does the following:
34 // (a) Transforms collection aggregate functions into a GroupBy.
35 // (b) Translates Exists(X) into Exists(select 1 from X)
37 namespace System.Data.Query.PlanCompiler
40 /// The normalizer performs transformations of the tree to bring it to a 'normalized' format
42 internal class Normalizer : SubqueryTrackingVisitor
45 private Normalizer(PlanCompiler planCompilerState)
46 :base(planCompilerState)
51 #region public methods
53 /// The driver routine.
55 /// <param name="planCompilerState">plan compiler state</param>
56 internal static void Process(PlanCompiler planCompilerState)
58 Normalizer normalizer = new Normalizer(planCompilerState);
64 #region private methods
67 private void Process()
69 m_command.Root = VisitNode(m_command.Root);
73 #region visitor methods
78 /// Translate Exists(X) into Exists(select 1 from X)
80 /// <param name="op"></param>
81 /// <param name="n"></param>
82 /// <returns></returns>
83 public override Node Visit(ExistsOp op, Node n)
87 // Build up a dummy project node over the input
88 n.Child0 = BuildDummyProjectForExists(n.Child0);
94 /// Build Project(select 1 from child).
96 /// <param name="child"></param>
97 /// <returns></returns>
98 private Node BuildDummyProjectForExists(Node child)
101 Node projectNode = m_command.BuildProject(
103 m_command.CreateNode(m_command.CreateInternalConstantOp(m_command.IntegerType, 1)),
109 /// Build up an unnest above a scalar op node
112 /// <param name="collectionNode">the scalarop collection node</param>
113 /// <returns>the unnest node</returns>
114 private Node BuildUnnest(Node collectionNode)
116 PlanCompiler.Assert(collectionNode.Op.IsScalarOp, "non-scalar usage of Unnest?");
117 PlanCompiler.Assert(TypeSemantics.IsCollectionType(collectionNode.Op.Type), "non-collection usage for Unnest?");
120 Node varDefNode = m_command.CreateVarDefNode(collectionNode, out newVar);
121 UnnestOp unnestOp = m_command.CreateUnnestOp(newVar);
122 Node unnestNode = m_command.CreateNode(unnestOp, varDefNode);
128 /// Converts the reference to a TVF as following: Collect(PhysicalProject(Unnest(Func)))
130 /// <param name="op">current function op</param>
131 /// <param name="n">current function subtree</param>
132 /// <returns>the new expression that corresponds to the TVF</returns>
133 private Node VisitCollectionFunction(FunctionOp op, Node n)
135 PlanCompiler.Assert(TypeSemantics.IsCollectionType(op.Type), "non-TVF function?");
137 Node unnestNode = BuildUnnest(n);
138 UnnestOp unnestOp = unnestNode.Op as UnnestOp;
139 PhysicalProjectOp projectOp = m_command.CreatePhysicalProjectOp(unnestOp.Table.Columns[0]);
140 Node projectNode = m_command.CreateNode(projectOp, unnestNode);
141 CollectOp collectOp = m_command.CreateCollectOp(n.Op.Type);
142 Node collectNode = m_command.CreateNode(collectOp, projectNode);
148 /// Converts a collection aggregate function count(X), where X is a collection into
149 /// two parts. Part A is a groupby subquery that looks like
150 /// GroupBy(Unnest(X), empty, count(y))
151 /// where "empty" describes the fact that the groupby has no keys, and y is an
152 /// element var of the Unnest
154 /// Part 2 is a VarRef that refers to the aggregate var for count(y) described above.
156 /// Logically, we would replace the entire functionOp by element(GroupBy...). However,
157 /// since we also want to translate element() into single-row-subqueries, we do this
160 /// The function itself is replaced by the VarRef, and the GroupBy is added to the list
161 /// of scalar subqueries for the current relOp node on the stack
164 /// <param name="op">the functionOp for the collection agg</param>
165 /// <param name="n">current subtree</param>
166 /// <returns>the VarRef node that should replace the function</returns>
167 private Node VisitCollectionAggregateFunction(FunctionOp op, Node n)
169 TypeUsage softCastType = null;
170 Node argNode = n.Child0;
171 if (OpType.SoftCast == argNode.Op.OpType)
173 softCastType = TypeHelpers.GetEdmType<CollectionType>(argNode.Op.Type).TypeUsage;
174 argNode = argNode.Child0;
176 while (OpType.SoftCast == argNode.Op.OpType)
178 argNode = argNode.Child0;
182 Node unnestNode = BuildUnnest(argNode);
183 UnnestOp unnestOp = unnestNode.Op as UnnestOp;
184 Var unnestOutputVar = unnestOp.Table.Columns[0];
186 AggregateOp aggregateOp = m_command.CreateAggregateOp(op.Function, false);
187 VarRefOp unnestVarRefOp = m_command.CreateVarRefOp(unnestOutputVar);
188 Node unnestVarRefNode = m_command.CreateNode(unnestVarRefOp);
189 if (softCastType != null)
191 unnestVarRefNode = m_command.CreateNode(m_command.CreateSoftCastOp(softCastType), unnestVarRefNode);
193 Node aggExprNode = m_command.CreateNode(aggregateOp, unnestVarRefNode);
195 VarVec keyVars = m_command.CreateVarVec(); // empty keys
196 Node keyVarDefListNode = m_command.CreateNode(m_command.CreateVarDefListOp());
198 VarVec gbyOutputVars = m_command.CreateVarVec();
200 Node aggVarDefListNode = m_command.CreateVarDefListNode(aggExprNode, out aggVar);
201 gbyOutputVars.Set(aggVar);
202 GroupByOp gbyOp = m_command.CreateGroupByOp(keyVars, gbyOutputVars);
203 Node gbySubqueryNode = m_command.CreateNode(gbyOp, unnestNode, keyVarDefListNode, aggVarDefListNode);
205 // "Move" this subquery to my parent relop
206 Node ret = AddSubqueryToParentRelOp(aggVar, gbySubqueryNode);
212 /// Pre-processing for a function. Does the default scalar op processing.
213 /// If the function returns a collection (TVF), the method converts this expression into
214 /// Collect(PhysicalProject(Unnest(Func))).
215 /// If the function is a collection aggregate, converts it into the corresponding group aggregate.
217 /// <param name="op"></param>
218 /// <param name="n"></param>
219 /// <returns></returns>
220 public override Node Visit(FunctionOp op, Node n)
222 VisitScalarOpDefault(op, n);
226 if (TypeSemantics.IsCollectionType(op.Type))
228 newNode = VisitCollectionFunction(op, n);
230 // Is this a collection-aggregate function?
231 else if (PlanCompilerUtil.IsCollectionAggregateFunction(op, n))
233 newNode = VisitCollectionAggregateFunction(op, n);
240 PlanCompiler.Assert(newNode != null, "failure to construct a functionOp?");
248 /// Processing for all JoinOps
250 /// <param name="op">JoinOp</param>
251 /// <param name="n">Current subtree</param>
252 /// <returns></returns>
253 protected override Node VisitJoinOp(JoinBaseOp op, Node n)
255 if (base.ProcessJoinOp(op, n))
257 // update the join condition
258 // #479372: Build up a dummy project node over the input, as we always wrap the child of exists
259 n.Child2.Child0 = BuildDummyProjectForExists(n.Child2.Child0);