public override void Visit(FunctionOp op, Node n) { VisitDefault(n); if (!PlanCompilerUtil.IsCollectionAggregateFunction(op, n)) { return; } if (n.Children.Count > 1) { return; } GroupAggregateVarInfo referencedGroupAggregateVarInfo; Node templateNode; bool isUnnested; if (GroupAggregateVarComputationTranslator.TryTranslateOverGroupAggregateVar( n.Child0, false, _command, _groupAggregateVarInfoManager, out referencedGroupAggregateVarInfo, out templateNode, out isUnnested) && (isUnnested || AggregatePushdownUtil.IsVarRefOverGivenVar(templateNode, referencedGroupAggregateVarInfo.GroupAggregateVar))) { referencedGroupAggregateVarInfo.CandidateAggregateNodes.Add(new KeyValuePair <Node, List <Node> >(n, new List <Node> { templateNode })); } }
private Node VisitCollectionAggregateFunction(FunctionOp op, Node n) { TypeUsage type = (TypeUsage)null; Node child0 = n.Child0; if (OpType.SoftCast == child0.Op.OpType) { type = TypeHelpers.GetEdmType <CollectionType>(child0.Op.Type).TypeUsage; child0 = child0.Child0; while (OpType.SoftCast == child0.Op.OpType) { child0 = child0.Child0; } } Node node1 = this.BuildUnnest(child0); Var column = (node1.Op as UnnestOp).Table.Columns[0]; AggregateOp aggregateOp = this.m_command.CreateAggregateOp(op.Function, false); Node node2 = this.m_command.CreateNode((Op)this.m_command.CreateVarRefOp(column)); if (type != null) { node2 = this.m_command.CreateNode((Op)this.m_command.CreateSoftCastOp(type), node2); } Node node3 = this.m_command.CreateNode((Op)aggregateOp, node2); VarVec varVec1 = this.m_command.CreateVarVec(); Node node4 = this.m_command.CreateNode((Op)this.m_command.CreateVarDefListOp()); VarVec varVec2 = this.m_command.CreateVarVec(); Var computedVar; Node varDefListNode = this.m_command.CreateVarDefListNode(node3, out computedVar); varVec2.Set(computedVar); Node node5 = this.m_command.CreateNode((Op)this.m_command.CreateGroupByOp(varVec1, varVec2), node1, node4, varDefListNode); return(this.AddSubqueryToParentRelOp(computedVar, node5)); }
internal static bool IsCollectionAggregateFunction(FunctionOp op, System.Data.Entity.Core.Query.InternalTrees.Node n) { if (n.Children.Count == 1 && TypeSemantics.IsCollectionType(n.Child0.Op.Type)) { return(TypeSemantics.IsAggregateFunction(op.Function)); } return(false); }
private Node VisitCollectionFunction(FunctionOp op, Node n) { System.Data.Entity.Core.Query.PlanCompiler.PlanCompiler.Assert(TypeSemantics.IsCollectionType(op.Type), "non-TVF function?"); Node node1 = this.BuildUnnest(n); Node node2 = this.m_command.CreateNode((Op)this.m_command.CreatePhysicalProjectOp((node1.Op as UnnestOp).Table.Columns[0]), node1); return(this.m_command.CreateNode((Op)this.m_command.CreateCollectOp(n.Op.Type), node2)); }
public override Node Visit(FunctionOp op, Node n) { this.VisitScalarOpDefault((ScalarOp)op, n); Node node = !TypeSemantics.IsCollectionType(op.Type) ? (!PlanCompilerUtil.IsCollectionAggregateFunction(op, n) ? n : this.VisitCollectionAggregateFunction(op, n)) : this.VisitCollectionFunction(op, n); System.Data.Entity.Core.Query.PlanCompiler.PlanCompiler.Assert(node != null, "failure to construct a functionOp?"); return(node); }
public override void Visit(FunctionOp op, Node n) { VisitScalarOpDefault(op, n); Assert(op.Function.Parameters.Count == n.Children.Count, "FunctionOp: Argument count ({0}) does not match parameter count ({1})", n.Children.Count, op.Function.Parameters.Count); for (int idx = 0; idx < n.Children.Count; idx++) { AssertEqualTypes(n.Children[idx].Op.Type, op.Function.Parameters[idx].TypeUsage); } }
/// <summary> /// Converts the reference to a TVF as following: Collect(PhysicalProject(Unnest(Func))) /// </summary> /// <param name="op">current function op</param> /// <param name="n">current function subtree</param> /// <returns>the new expression that corresponds to the TVF</returns> private Node VisitCollectionFunction(FunctionOp op, Node n) { PlanCompiler.Assert(TypeSemantics.IsCollectionType(op.Type), "non-TVF function?"); Node unnestNode = BuildUnnest(n); UnnestOp unnestOp = unnestNode.Op as UnnestOp; PhysicalProjectOp projectOp = m_command.CreatePhysicalProjectOp(unnestOp.Table.Columns[0]); Node projectNode = m_command.CreateNode(projectOp, unnestNode); CollectOp collectOp = m_command.CreateCollectOp(n.Op.Type); Node collectNode = m_command.CreateNode(collectOp, projectNode); return(collectNode); }
/// <summary> /// Converts a collection aggregate function count(X), where X is a collection into /// two parts. Part A is a groupby subquery that looks like /// GroupBy(Unnest(X), empty, count(y)) /// where "empty" describes the fact that the groupby has no keys, and y is an /// element var of the Unnest /// Part 2 is a VarRef that refers to the aggregate var for count(y) described above. /// Logically, we would replace the entire functionOp by element(GroupBy...). However, /// since we also want to translate element() into single-row-subqueries, we do this /// here as well. /// The function itself is replaced by the VarRef, and the GroupBy is added to the list /// of scalar subqueries for the current relOp node on the stack /// </summary> /// <param name="op"> the functionOp for the collection agg </param> /// <param name="n"> current subtree </param> /// <returns> the VarRef node that should replace the function </returns> private Node VisitCollectionAggregateFunction(FunctionOp op, Node n) { TypeUsage softCastType = null; var argNode = n.Child0; if (OpType.SoftCast == argNode.Op.OpType) { softCastType = TypeHelpers.GetEdmType <CollectionType>(argNode.Op.Type).TypeUsage; argNode = argNode.Child0; while (OpType.SoftCast == argNode.Op.OpType) { argNode = argNode.Child0; } } var unnestNode = BuildUnnest(argNode); var unnestOp = unnestNode.Op as UnnestOp; var unnestOutputVar = unnestOp.Table.Columns[0]; var aggregateOp = m_command.CreateAggregateOp(op.Function, false); var unnestVarRefOp = m_command.CreateVarRefOp(unnestOutputVar); var unnestVarRefNode = m_command.CreateNode(unnestVarRefOp); if (softCastType != null) { unnestVarRefNode = m_command.CreateNode(m_command.CreateSoftCastOp(softCastType), unnestVarRefNode); } var aggExprNode = m_command.CreateNode(aggregateOp, unnestVarRefNode); var keyVars = m_command.CreateVarVec(); // empty keys var keyVarDefListNode = m_command.CreateNode(m_command.CreateVarDefListOp()); var gbyOutputVars = m_command.CreateVarVec(); Var aggVar; var aggVarDefListNode = m_command.CreateVarDefListNode(aggExprNode, out aggVar); gbyOutputVars.Set(aggVar); var gbyOp = m_command.CreateGroupByOp(keyVars, gbyOutputVars); var gbySubqueryNode = m_command.CreateNode(gbyOp, unnestNode, keyVarDefListNode, aggVarDefListNode); // "Move" this subquery to my parent relop var ret = AddSubqueryToParentRelOp(aggVar, gbySubqueryNode); return(ret); }
private void TryProcessCandidate( KeyValuePair <System.Data.Entity.Core.Query.InternalTrees.Node, System.Data.Entity.Core.Query.InternalTrees.Node> candidate, GroupAggregateVarInfo groupAggregateVarInfo) { System.Data.Entity.Core.Query.InternalTrees.Node definingGroupNode = groupAggregateVarInfo.DefiningGroupNode; IList <System.Data.Entity.Core.Query.InternalTrees.Node> ancestors1; IList <System.Data.Entity.Core.Query.InternalTrees.Node> ancestors2; this.FindPathsToLeastCommonAncestor(candidate.Key, definingGroupNode, out ancestors1, out ancestors2); if (!AggregatePushdown.AreAllNodesSupportedForPropagation(ancestors2)) { return; } GroupByIntoOp op1 = (GroupByIntoOp)definingGroupNode.Op; System.Data.Entity.Core.Query.PlanCompiler.PlanCompiler.Assert(op1.Inputs.Count == 1, "There should be one input var to GroupByInto at this stage"); Var first = op1.Inputs.First; FunctionOp op2 = (FunctionOp)candidate.Key.Op; System.Data.Entity.Core.Query.InternalTrees.Node subTree = OpCopier.Copy(this.m_command, candidate.Value); new VarRemapper(this.m_command, new Dictionary <Var, Var>(1) { { groupAggregateVarInfo.GroupAggregateVar, first } }).RemapSubtree(subTree); Var computedVar; System.Data.Entity.Core.Query.InternalTrees.Node varDefNode = this.m_command.CreateVarDefNode(this.m_command.CreateNode((Op)this.m_command.CreateAggregateOp(op2.Function, false), subTree), out computedVar); definingGroupNode.Child2.Children.Add(varDefNode); ((GroupByBaseOp)definingGroupNode.Op).Outputs.Set(computedVar); for (int index = 0; index < ancestors2.Count; ++index) { System.Data.Entity.Core.Query.InternalTrees.Node node = ancestors2[index]; if (node.Op.OpType == OpType.Project) { ((ProjectOp)node.Op).Outputs.Set(computedVar); } } candidate.Key.Op = (Op)this.m_command.CreateVarRefOp(computedVar); candidate.Key.Children.Clear(); }
public override void Visit(FunctionOp op, System.Data.Entity.Core.Query.InternalTrees.Node n) { this.VisitDefault(n); if (!PlanCompilerUtil.IsCollectionAggregateFunction(op, n)) { return; } System.Data.Entity.Core.Query.PlanCompiler.PlanCompiler.Assert(n.Children.Count == 1, "Aggregate Function must have one argument"); System.Data.Entity.Core.Query.InternalTrees.Node child0 = n.Child0; GroupAggregateVarInfo groupAggregateVarInfo; System.Data.Entity.Core.Query.InternalTrees.Node templateNode; bool isUnnested; if (!GroupAggregateVarComputationTranslator.TryTranslateOverGroupAggregateVar(n.Child0, false, this._command, this._groupAggregateVarInfoManager, out groupAggregateVarInfo, out templateNode, out isUnnested) || !isUnnested && !AggregatePushdownUtil.IsVarRefOverGivenVar(templateNode, groupAggregateVarInfo.GroupAggregateVar)) { return; } groupAggregateVarInfo.CandidateAggregateNodes.Add(new KeyValuePair <System.Data.Entity.Core.Query.InternalTrees.Node, System.Data.Entity.Core.Query.InternalTrees.Node>(n, templateNode)); }
/// <summary> /// Pre-processing for a function. Does the default scalar op processing. /// If the function returns a collection (TVF), the method converts this expression into /// Collect(PhysicalProject(Unnest(Func))). /// If the function is a collection aggregate, converts it into the corresponding group aggregate. /// </summary> /// <param name="op"></param> /// <param name="n"></param> /// <returns></returns> public override Node Visit(FunctionOp op, Node n) { VisitScalarOpDefault(op, n); Node newNode = null; // Is this a TVF? if (TypeSemantics.IsCollectionType(op.Type)) { newNode = VisitCollectionFunction(op, n); } // Is this a collection-aggregate function? else if (PlanCompilerUtil.IsCollectionAggregateFunction(op, n)) { newNode = VisitCollectionAggregateFunction(op, n); } else { newNode = n; } PlanCompiler.Assert(newNode != null, "failure to construct a functionOp?"); return(newNode); }
public override void Visit(FunctionOp op, Node n) { VisitDefault(n); if (!PlanCompilerUtil.IsCollectionAggregateFunction(op, n)) { return; } PlanCompiler.Assert(n.Children.Count == 1, "Aggregate Function must have one argument"); var argumentNode = n.Child0; GroupAggregateVarInfo referencedGroupAggregateVarInfo; Node templateNode; bool isUnnested; if (GroupAggregateVarComputationTranslator.TryTranslateOverGroupAggregateVar( n.Child0, false, _command, _groupAggregateVarInfoManager, out referencedGroupAggregateVarInfo, out templateNode, out isUnnested) && (isUnnested || AggregatePushdownUtil.IsVarRefOverGivenVar(templateNode, referencedGroupAggregateVarInfo.GroupAggregateVar))) { referencedGroupAggregateVarInfo.CandidateAggregateNodes.Add(new KeyValuePair <Node, Node>(n, templateNode)); } }
private Node VisitCollectionFunction(FunctionOp op, Node n) { PlanCompiler.Assert(TypeSemantics.IsCollectionType(op.Type), "non-TVF function?"); var unnestNode = BuildUnnest(n); var unnestOp = unnestNode.Op as UnnestOp; var projectOp = m_command.CreatePhysicalProjectOp(unnestOp.Table.Columns[0]); var projectNode = m_command.CreateNode(projectOp, unnestNode); var collectOp = m_command.CreateCollectOp(n.Op.Type); var collectNode = m_command.CreateNode(collectOp, projectNode); return collectNode; }
public override void Visit(FunctionOp op, Node n) { VisitScalarOpDefault(op, n); Assert( op.Function.Parameters.Count == n.Children.Count, "FunctionOp: Argument count ({0}) does not match parameter count ({1})", n.Children.Count, op.Function.Parameters.Count); for (var idx = 0; idx < n.Children.Count; idx++) { AssertEqualTypes(n.Children[idx].Op.Type, op.Function.Parameters[idx].TypeUsage); } }
public override Node Visit(FunctionOp op, Node n) { if (op.Function.IsFunctionImport) { PlanCompiler.Assert( op.Function.IsComposableAttribute, "Cannot process a non-composable function inside query tree composition."); FunctionImportMapping functionImportMapping = null; if (!m_command.MetadataWorkspace.TryGetFunctionImportMapping(op.Function, out functionImportMapping)) { throw new MetadataException(Strings.EntityClient_UnmappedFunctionImport(op.Function.FullName)); } PlanCompiler.Assert( functionImportMapping is FunctionImportMappingComposable, "Composable function import must have corresponding mapping."); var functionImportMappingComposable = (FunctionImportMappingComposable)functionImportMapping; // Visit children (function call arguments) before processing the function view. // Visiting argument trees before the view tree is required because we want to process them first // outside of the context of the view. For example if an argument tree contains a free-floating entity-type constructor // and the function mapping scopes the function results to a particular entity set, we don't want // the free-floating constructor to be auto-scoped to this set. So we process the argument first, it will // scope the constructor to the null scope and which guarantees that this constructor will not be rescoped after the argument // tree is embedded into the function view inside the functionMapping.GetInternalTree(...) call. VisitChildren(n); // Get the mapping view of the function. var ret = functionImportMappingComposable.GetInternalTree(m_command, n.Children); // Push the entity type scope, if any, before processing the view. if (op.Function.EntitySet != null) { m_entityTypeScopes.Push(op.Function.EntitySet); AddEntitySetReference(op.Function.EntitySet); PlanCompiler.Assert( functionImportMappingComposable.TvfKeys != null && functionImportMappingComposable.TvfKeys.Length > 0, "Function imports returning entities must have inferred keys."); if (!m_tvfResultKeys.ContainsKey(functionImportMappingComposable.TargetFunction)) { m_tvfResultKeys.Add(functionImportMappingComposable.TargetFunction, functionImportMappingComposable.TvfKeys); } } // Rerun the processor over the resulting subtree. ret = VisitNode(ret); // Remove the entity type scope, if any. if (op.Function.EntitySet != null) { var scope = m_entityTypeScopes.Pop(); PlanCompiler.Assert(scope == op.Function.EntitySet, "m_entityTypeScopes stack is broken"); } return ret; } else { PlanCompiler.Assert(op.Function.EntitySet == null, "Entity type scope is not supported on functions that aren't mapped."); // If this is TVF or a collection aggregate, function NestPullUp and Normalization are needed. if (TypeSemantics.IsCollectionType(op.Type) || PlanCompilerUtil.IsCollectionAggregateFunction(op, n)) { m_compilerState.MarkPhaseAsNeeded(PlanCompilerPhase.NestPullup); m_compilerState.MarkPhaseAsNeeded(PlanCompilerPhase.Normalization); } return base.Visit(op, n); } }
/// <summary> /// Is this function a collection aggregate function. It is, if /// - it has exactly one child /// - that child is a collection type /// - and the function has been marked with the aggregate attribute /// </summary> /// <param name="op"> the function op </param> /// <param name="n"> the current subtree </param> /// <returns> true, if this was a collection aggregate function </returns> internal static bool IsCollectionAggregateFunction(FunctionOp op, Node n) { return((n.Children.Count == 1) && TypeSemantics.IsCollectionType(n.Child0.Op.Type) && TypeSemantics.IsAggregateFunction(op.Function)); }
/// <summary> /// Is this function a collection aggregate function. It is, if /// - it has exactly one child /// - that child is a collection type /// - and the function has been marked with the aggregate attribute /// </summary> /// <param name="op">the function op</param> /// <param name="n">the current subtree</param> /// <returns>true, if this was a collection aggregate function</returns> internal static bool IsCollectionAggregateFunction(FunctionOp op, Node n) { return ((n.Children.Count == 1) && TypeSemantics.IsCollectionType(n.Child0.Op.Type) && TypeSemantics.IsAggregateFunction(op.Function)); }
public override void Visit(FunctionOp op, Node n) { VisitDefault(n); if (!PlanCompilerUtil.IsCollectionAggregateFunction(op, n)) { return; } PlanCompiler.Assert(n.Children.Count == 1, "Aggregate Function must have one argument"); var argumentNode = n.Child0; GroupAggregateVarInfo referencedGroupAggregateVarInfo; Node templateNode; bool isUnnested; if (GroupAggregateVarComputationTranslator.TryTranslateOverGroupAggregateVar( n.Child0, false, _command, _groupAggregateVarInfoManager, out referencedGroupAggregateVarInfo, out templateNode, out isUnnested) && (isUnnested || AggregatePushdownUtil.IsVarRefOverGivenVar(templateNode, referencedGroupAggregateVarInfo.GroupAggregateVar))) { referencedGroupAggregateVarInfo.CandidateAggregateNodes.Add(new KeyValuePair<Node, Node>(n, templateNode)); } }
/// <summary> /// Copies a FunctionOp /// </summary> /// <param name="op">The Op to Copy</param> /// <param name="n">The Node that references the Op</param> /// <returns>A copy of the original Node that references a copy of the original Op</returns> public override Node Visit(FunctionOp op, Node n) { return(CopyDefault(m_destCmd.CreateFunctionOp(op.Function), n)); }
/// <summary> /// Visitor pattern method for FunctionOp /// </summary> /// <param name="op"> The FunctionOp being visited </param> /// <param name="n"> The Node that references the Op </param> public virtual void Visit(FunctionOp op, Node n) { VisitScalarOpDefault(op, n); }
// <summary> // Copies a FunctionOp // </summary> // <param name="op"> The Op to Copy </param> // <param name="n"> The Node that references the Op </param> // <returns> A copy of the original Node that references a copy of the original Op </returns> public override Node Visit(FunctionOp op, Node n) { return CopyDefault(m_destCmd.CreateFunctionOp(op.Function), n); }
/// <summary> /// Converts a collection aggregate function count(X), where X is a collection into /// two parts. Part A is a groupby subquery that looks like /// GroupBy(Unnest(X), empty, count(y)) /// where "empty" describes the fact that the groupby has no keys, and y is an /// element var of the Unnest /// Part 2 is a VarRef that refers to the aggregate var for count(y) described above. /// Logically, we would replace the entire functionOp by element(GroupBy...). However, /// since we also want to translate element() into single-row-subqueries, we do this /// here as well. /// The function itself is replaced by the VarRef, and the GroupBy is added to the list /// of scalar subqueries for the current relOp node on the stack /// </summary> /// <param name="op"> the functionOp for the collection agg </param> /// <param name="n"> current subtree </param> /// <returns> the VarRef node that should replace the function </returns> private Node VisitCollectionAggregateFunction(FunctionOp op, Node n) { TypeUsage softCastType = null; var argNode = n.Child0; if (OpType.SoftCast == argNode.Op.OpType) { softCastType = TypeHelpers.GetEdmType<CollectionType>(argNode.Op.Type).TypeUsage; argNode = argNode.Child0; while (OpType.SoftCast == argNode.Op.OpType) { argNode = argNode.Child0; } } var unnestNode = BuildUnnest(argNode); var unnestOp = unnestNode.Op as UnnestOp; var unnestOutputVar = unnestOp.Table.Columns[0]; var aggregateOp = m_command.CreateAggregateOp(op.Function, false); var unnestVarRefOp = m_command.CreateVarRefOp(unnestOutputVar); var unnestVarRefNode = m_command.CreateNode(unnestVarRefOp); if (softCastType != null) { unnestVarRefNode = m_command.CreateNode(m_command.CreateSoftCastOp(softCastType), unnestVarRefNode); } var aggExprNode = m_command.CreateNode(aggregateOp, unnestVarRefNode); var keyVars = m_command.CreateVarVec(); // empty keys var keyVarDefListNode = m_command.CreateNode(m_command.CreateVarDefListOp()); var gbyOutputVars = m_command.CreateVarVec(); Var aggVar; var aggVarDefListNode = m_command.CreateVarDefListNode(aggExprNode, out aggVar); gbyOutputVars.Set(aggVar); var gbyOp = m_command.CreateGroupByOp(keyVars, gbyOutputVars); var gbySubqueryNode = m_command.CreateNode(gbyOp, unnestNode, keyVarDefListNode, aggVarDefListNode); // "Move" this subquery to my parent relop var ret = AddSubqueryToParentRelOp(aggVar, gbySubqueryNode); return ret; }
public override Node Visit(FunctionOp op, Node n) { VisitScalarOpDefault(op, n); Node newNode = null; // Is this a TVF? if (TypeSemantics.IsCollectionType(op.Type)) { newNode = VisitCollectionFunction(op, n); } // Is this a collection-aggregate function? else if (PlanCompilerUtil.IsCollectionAggregateFunction(op, n)) { newNode = VisitCollectionAggregateFunction(op, n); } else { newNode = n; } PlanCompiler.Assert(newNode != null, "failure to construct a functionOp?"); return newNode; }
/// <summary> /// Try to push the given function aggregate candidate to the corresponding group into node. /// The candidate can be pushed if all ancestors of the group into node up to the least common /// ancestor between the group into node and the function aggregate have one of the following node op types: /// Project /// Filter /// ConstraintSortOp /// </summary> /// <param name="command"></param> /// <param name="candidate"></param> /// <param name="groupAggregateVarInfo"></param> /// <param name="m_childToParent"></param> private void TryProcessCandidate( KeyValuePair <Node, Node> candidate, GroupAggregateVarInfo groupAggregateVarInfo) { IList <Node> functionAncestors; IList <Node> groupByAncestors; Node definingGroupNode = groupAggregateVarInfo.DefiningGroupNode; FindPathsToLeastCommonAncestor(candidate.Key, definingGroupNode, out functionAncestors, out groupByAncestors); //Check whether all ancestors of the GroupByInto node are of type that we support propagating through if (!AreAllNodesSupportedForPropagation(groupByAncestors)) { return; } //Add the function to the group by node GroupByIntoOp definingGroupOp = (GroupByIntoOp)definingGroupNode.Op; PlanCompiler.Assert(definingGroupOp.Inputs.Count == 1, "There should be one input var to GroupByInto at this stage"); Var inputVar = definingGroupOp.Inputs.First; FunctionOp functionOp = (FunctionOp)candidate.Key.Op; // // Remap the template from referencing the groupAggregate var to reference the input to // the group by into // Node argumentNode = OpCopier.Copy(m_command, candidate.Value); Dictionary <Var, Var> dictionary = new Dictionary <Var, Var>(1); dictionary.Add(groupAggregateVarInfo.GroupAggregateVar, inputVar); VarRemapper remapper = new VarRemapper(m_command, dictionary); remapper.RemapSubtree(argumentNode); Node newFunctionDefiningNode = m_command.CreateNode( m_command.CreateAggregateOp(functionOp.Function, false), argumentNode); Var newFunctionVar; Node varDefNode = m_command.CreateVarDefNode(newFunctionDefiningNode, out newFunctionVar); // Add the new aggregate to the list of aggregates definingGroupNode.Child2.Children.Add(varDefNode); GroupByIntoOp groupByOp = (GroupByIntoOp)definingGroupNode.Op; groupByOp.Outputs.Set(newFunctionVar); //Propagate the new var throught the ancestors of the GroupByInto for (int i = 0; i < groupByAncestors.Count; i++) { Node groupByAncestor = groupByAncestors[i]; if (groupByAncestor.Op.OpType == OpType.Project) { ProjectOp ancestorProjectOp = (ProjectOp)groupByAncestor.Op; ancestorProjectOp.Outputs.Set(newFunctionVar); } } //Update the functionNode candidate.Key.Op = m_command.CreateVarRefOp(newFunctionVar); candidate.Key.Children.Clear(); }