Example #1
0
        public override void Visit(UnnestOp op, Node n)
        {
            VisitRelOpDefault(op, n);
            var newVar = Map(op.Var);

            if (newVar != op.Var)
            {
                n.Op = m_command.CreateUnnestOp(newVar, op.Table);
            }
        }
        /// <summary>
        /// If the unnestOp's var is defined as a reference of a group aggregate var,
        /// then the columns it produces should be registered too, but as 'unnested' references
        /// </summary>
        /// <param name="op">the unnestOp</param>
        /// <param name="n">current subtree</param>
        /// <returns>modified subtree</returns>
        public override void Visit(UnnestOp op, Node n)
        {
            VisitDefault(n);
            GroupAggregateVarRefInfo groupAggregateVarRefInfo;

            if (_groupAggregateVarInfoManager.TryGetReferencedGroupAggregateVarInfo(op.Var, out groupAggregateVarRefInfo))
            {
                PlanCompiler.Assert(op.Table.Columns.Count == 1, "Expected one column before NTE");
                _groupAggregateVarInfoManager.Add(op.Table.Columns[0], groupAggregateVarRefInfo.GroupAggregateVarInfo, groupAggregateVarRefInfo.Computation, true);
            }
        }
Example #3
0
        public override void Visit(UnnestOp op, System.Data.Entity.Core.Query.InternalTrees.Node n)
        {
            this.VisitRelOpDefault((RelOp)op, n);
            Var v = this.Map(op.Var);

            if (v == op.Var)
            {
                return;
            }
            n.Op = (Op)this.m_command.CreateUnnestOp(v, op.Table);
        }
Example #4
0
        public override System.Data.Entity.Core.Query.InternalTrees.Node Visit(UnnestOp op, System.Data.Entity.Core.Query.InternalTrees.Node n)
        {
            this.VisitChildren(n);
            List <System.Data.Entity.Core.Query.InternalTrees.Node> subqueries;

            if (this.m_nodeSubqueries.TryGetValue(n, out subqueries))
            {
                return(this.AugmentWithSubqueries(n, subqueries, false));
            }
            return(n);
        }
Example #5
0
        /// <summary>
        /// Build up an unnest above a scalar op node
        ///    X => unnest(X)
        /// </summary>
        /// <param name="collectionNode">the scalarop collection node</param>
        /// <returns>the unnest node</returns>
        private Node BuildUnnest(Node collectionNode)
        {
            PlanCompiler.Assert(collectionNode.Op.IsScalarOp, "non-scalar usage of Unnest?");
            PlanCompiler.Assert(TypeSemantics.IsCollectionType(collectionNode.Op.Type), "non-collection usage for Unnest?");

            Var      newVar;
            Node     varDefNode = m_command.CreateVarDefNode(collectionNode, out newVar);
            UnnestOp unnestOp   = m_command.CreateUnnestOp(newVar);
            Node     unnestNode = m_command.CreateNode(unnestOp, varDefNode);

            return(unnestNode);
        }
Example #6
0
        public override void Visit(UnnestOp op, System.Data.Entity.Core.Query.InternalTrees.Node n)
        {
            this.VisitDefault(n);
            GroupAggregateVarRefInfo groupAggregateVarRefInfo;

            if (!this._groupAggregateVarInfoManager.TryGetReferencedGroupAggregateVarInfo(op.Var, out groupAggregateVarRefInfo))
            {
                return;
            }
            System.Data.Entity.Core.Query.PlanCompiler.PlanCompiler.Assert(op.Table.Columns.Count == 1, "Expected one column before NTE");
            this._groupAggregateVarInfoManager.Add(op.Table.Columns[0], groupAggregateVarRefInfo.GroupAggregateVarInfo, groupAggregateVarRefInfo.Computation, true);
        }
Example #7
0
        /// <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);
        }
Example #8
0
        /// <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;
            Node      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;
                }
            }

            Node     unnestNode      = BuildUnnest(argNode);
            UnnestOp unnestOp        = unnestNode.Op as UnnestOp;
            Var      unnestOutputVar = unnestOp.Table.Columns[0];

            AggregateOp aggregateOp      = m_command.CreateAggregateOp(op.Function, false);
            VarRefOp    unnestVarRefOp   = m_command.CreateVarRefOp(unnestOutputVar);
            Node        unnestVarRefNode = m_command.CreateNode(unnestVarRefOp);

            if (softCastType != null)
            {
                unnestVarRefNode = m_command.CreateNode(m_command.CreateSoftCastOp(softCastType), unnestVarRefNode);
            }
            Node aggExprNode = m_command.CreateNode(aggregateOp, unnestVarRefNode);

            VarVec keyVars           = m_command.CreateVarVec(); // empty keys
            Node   keyVarDefListNode = m_command.CreateNode(m_command.CreateVarDefListOp());

            VarVec gbyOutputVars = m_command.CreateVarVec();
            Var    aggVar;
            Node   aggVarDefListNode = m_command.CreateVarDefListNode(aggExprNode, out aggVar);

            gbyOutputVars.Set(aggVar);
            GroupByOp gbyOp           = m_command.CreateGroupByOp(keyVars, gbyOutputVars);
            Node      gbySubqueryNode = m_command.CreateNode(gbyOp, unnestNode, keyVarDefListNode, aggVarDefListNode);

            // "Move" this subquery to my parent relop
            Node ret = AddSubqueryToParentRelOp(aggVar, gbySubqueryNode);

            return(ret);
        }
Example #9
0
        /// <summary>
        /// Visitor for UnnestOp. If the child has any subqueries, we need to convert this
        /// into an
        ///    OuterApply(S, Unnest)
        /// unlike the other cases where the OuterApply will appear as the input of the node
        /// </summary>
        /// <param name="op">the unnestOp</param>
        /// <param name="n">current subtree</param>
        /// <returns>modified subtree</returns>
        public override Node Visit(UnnestOp op, Node n)
        {
            VisitChildren(n); // visit all my children first

            List <Node> nestedSubqueries;

            if (m_nodeSubqueries.TryGetValue(n, out nestedSubqueries))
            {
                // We pass 'inputFirst = false' since the subqueries contribute to the driver in the unnest,
                // they are not generated by the unnest.
                Node newNode = AugmentWithSubqueries(n, nestedSubqueries, false /* inputFirst */);
                return(newNode);
            }
            else
            {
                return(n);
            }
        }
        public override Node Visit(UnnestOp op, Node n)
        {
            // Visit the children first
            VisitChildren(n);

            Var newUnnestVar = null;
            md.EdmFunction processingTVF = null;

            if (n.HasChild0)
            {
                var chi = n.Child0;
                var varDefOp = chi.Op as VarDefOp;

                if (null != varDefOp)
                {
                    if (TypeUtils.IsCollectionType(varDefOp.Var.Type))
                    {
                        var computedVar = (ComputedVar)varDefOp.Var;

                        if (chi.HasChild0
                            && chi.Child0.Op.OpType == OpType.Function)
                        {
                            // For a TVF function call use the original non-flattened variable:
                            // the function will not return properties described in the flattened type, there would be no null sentinel and 
                            // row prop names will be as declared in the function signature.
                            // The mismatch between the flattened type and the orignial type is fixed by wrapping into a ProjectOp produced by CreateTVFProjection(...).
                            newUnnestVar = computedVar;
                            processingTVF = ((FunctionOp)chi.Child0.Op).Function;
                        }
                        else
                        {
                            // Flatten the computer var and add it to m_varInfoMap.
                            var newChildren = new List<Node>();
                            md.TypeUsage newType;
                            FlattenComputedVar(computedVar, chi, out newChildren, out newType);
                            PlanCompiler.Assert(newChildren.Count == 1, "Flattening unnest var produced more than one Var.");
                            n.Child0 = newChildren[0];
                        }
                    }
                }
            }

            if (processingTVF != null)
            {
                PlanCompiler.Assert(newUnnestVar != null, "newUnnestVar must be initialized in the TVF case.");
            }
            else
            {
                // Fetch the new unnestVar that should have been prepared inside the FlattenComputedVar call above.
                // If the new var info is not ready then the shape of the unnest or the variable type is incorrect.
                VarInfo unnestVarInfo;
                if (m_varInfoMap.TryGetVarInfo(op.Var, out unnestVarInfo)
                    && unnestVarInfo.Kind == VarInfoKind.CollectionVarInfo)
                {
                    newUnnestVar = ((CollectionVarInfo)unnestVarInfo).NewVar;
                }
                else
                {
                    throw new InvalidOperationException(Strings.ADP_InternalProviderError((int)EntityUtil.InternalErrorCode.WrongVarType));
                }
            }

            // If the type of table column var representing the collection element is not structured, simply update the n.Op with the new var and return n.
            // Otherwise rebuild the UnnestOp based on the new flattened type of the new input var fetched above.
            // If the input var represents a TVF call, then wrap the newly rebuilt UnnestOp into a ProjectOp (see below for more details).
            var unnestTableColumnVar = op.Table.Columns[0];
            if (!TypeUtils.IsStructuredType(unnestTableColumnVar.Type))
            {
                PlanCompiler.Assert(
                    processingTVF == null, "TVFs returning a collection of values of a non-structured type are not supported");

                if (md.TypeSemantics.IsEnumerationType(unnestTableColumnVar.Type)
                    || md.TypeSemantics.IsStrongSpatialType(unnestTableColumnVar.Type))
                {
                    var unnestOp = m_command.CreateUnnestOp(newUnnestVar);
                    m_varInfoMap.CreatePrimitiveTypeVarInfo(unnestTableColumnVar, unnestOp.Table.Columns[0]);
                    n.Op = unnestOp;
                }
                else
                {
                    // Update the current unnest node with the new UnnestOp based on the newUnnestVar and the old table.
                    n.Op = m_command.CreateUnnestOp(newUnnestVar, op.Table);
                }
            }
            else
            {
                //
                // 1. Flatten out the table to be used in the new UnnestOp.
                //    If processingTVF use the typeInfo.FlattenedType for the new table structure,
                //    otherwise use the original type of the unnestTableColumnVar representing precisely the fields returned by the TVF.
                //
                // 2. Create the new UnnestOp using the new unnest input var and the new table.
                //    Note that if processingTVF, the new unnest input var is not flattened (see code above for more info).
                //
                // 3. If processingTVF, create a ProjectOp and wrap the new UnnestOp into it.
                //    The new ProjectOp projects fields of the typeInfo.FlattenedType. The values of the projected fields
                //    are taken from the corresponding variables of the new UnnestOp. 
                //    The new ProjectOp also projects a null sentinenel if the flattened type has one.
                //
                // 4. Update m_varInfoMap with the new new entry that maps the old unnestTableColumnVar to the list of new flattened vars:
                //    If processingTVF, the new flattended vars are the outputs of the ProjectOp, 
                //    otherwise the new flattened vars are the columns on the new UnnestOp.Table.
                //

                //
                // Get the flattened representation of the table column var type.
                //
                var typeInfo = m_typeInfo.GetTypeInfo(unnestTableColumnVar.Type);

                TableMD newTableMetadata;
                if (processingTVF != null)
                {
                    // For the direct function call use the original non-flattened type.
                    // The function will not return values according to the flattened type:
                    // there would be no null sentinel and row prop names will be as declared in the function signature.
                    // In the code below we create a projection over the function call that produces the flattened.
                    var tvfReturnType = TypeHelpers.GetTvfReturnType(processingTVF);
                    PlanCompiler.Assert(
                        Command.EqualTypes(tvfReturnType, unnestTableColumnVar.Type.EdmType),
                        "Unexpected TVF return type (row type is expected).");
                    newTableMetadata = m_command.CreateFlatTableDefinition(tvfReturnType.Properties, GetTvfResultKeys(processingTVF), null);
                }
                else
                {
                    newTableMetadata = m_command.CreateFlatTableDefinition(typeInfo.FlattenedType);
                }
                var newTable = m_command.CreateTableInstance(newTableMetadata);

                // Update the current unnest node with the new UnnestOp based on the newUnnestVar and newTable.
                n.Op = m_command.CreateUnnestOp(newUnnestVar, newTable);
                List<Var> newVars;
                if (processingTVF != null)
                {
                    // Replace the current Unnest(Func()) with the new Project(Unnest(Func()))
                    n = CreateTVFProjection(n, newTable.Columns, typeInfo, out newVars);
                }
                else
                {
                    newVars = newTable.Columns;
                }

                // Map the unnestTableColumnVar to the list of the new flattened vars.
                m_varInfoMap.CreateStructuredVarInfo(
                    unnestTableColumnVar,
                    typeInfo.FlattenedType,
                    newVars,
                    typeInfo.FlattenedType.Properties.ToList());
            }

            return n;
        }
        /// <summary>
        ///     Visitor for UnnestOp. If the child has any subqueries, we need to convert this
        ///     into an
        ///     OuterApply(S, Unnest)
        ///     unlike the other cases where the OuterApply will appear as the input of the node
        /// </summary>
        /// <param name="op"> the unnestOp </param>
        /// <param name="n"> current subtree </param>
        /// <returns> modified subtree </returns>
        public override Node Visit(UnnestOp op, Node n)
        {
            VisitChildren(n); // visit all my children first

            List<Node> nestedSubqueries;
            if (m_nodeSubqueries.TryGetValue(n, out nestedSubqueries))
            {
                // We pass 'inputFirst = false' since the subqueries contribute to the driver in the unnest,
                // they are not generated by the unnest.
                var newNode = AugmentWithSubqueries(n, nestedSubqueries, false /* inputFirst */);
                return newNode;
            }
            else
            {
                return n;
            }
        }
 public override void Visit(UnnestOp op, Node n)
 {
     VisitRelOpDefault(op, n);
     AssertOpType(n.Child0.Op, OpType.VarDef);
 }
Example #13
0
        /// <summary>
        /// If the Subtree rooted at the collect is of the following structure:
        ///
        /// PhysicalProject(outputVar)
        /// |
        /// Project(s)
        /// |
        /// Unnest
        ///
        /// where the unnest is over the group aggregate var and the output var
        /// is either a reference to the group aggregate var or to a constant, it returns the
        /// translation of the ouput var.
        /// </summary>
        /// <param name="n"></param>
        /// <returns></returns>
        private Node VisitCollect(Node n)
        {
            //Make sure the only children are projects over unnest
            Node currentNode = n.Child0;
            Dictionary <Var, Node> constantDefinitions = new Dictionary <Var, Node>();

            while (currentNode.Child0.Op.OpType == OpType.Project)
            {
                currentNode = currentNode.Child0;
                //Visit the VarDefListOp child
                if (VisitDefault(currentNode.Child1) == null)
                {
                    return(null);
                }
                foreach (Node definitionNode in currentNode.Child1.Children)
                {
                    if (IsConstant(definitionNode.Child0))
                    {
                        constantDefinitions.Add(((VarDefOp)definitionNode.Op).Var, definitionNode.Child0);
                    }
                }
            }

            if (currentNode.Child0.Op.OpType != OpType.Unnest)
            {
                return(null);
            }

            // Handle the unnest
            UnnestOp unnestOp = (UnnestOp)currentNode.Child0.Op;
            GroupAggregateVarRefInfo groupAggregateVarRefInfo;

            if (_groupAggregateVarInfoManager.TryGetReferencedGroupAggregateVarInfo(unnestOp.Var, out groupAggregateVarRefInfo))
            {
                if (_targetGroupAggregateVarInfo == null)
                {
                    _targetGroupAggregateVarInfo = groupAggregateVarRefInfo.GroupAggregateVarInfo;
                }
                else if (_targetGroupAggregateVarInfo != groupAggregateVarRefInfo.GroupAggregateVarInfo)
                {
                    return(null);
                }
                if (!_isUnnested)
                {
                    return(null);
                }
            }
            else
            {
                return(null);
            }

            PhysicalProjectOp physicalProjectOp = (PhysicalProjectOp)n.Child0.Op;

            PlanCompiler.Assert(physicalProjectOp.Outputs.Count == 1, "PhysicalProject should only have one output at this stage");
            Var outputVar = physicalProjectOp.Outputs[0];

            Node computationTemplate = TranslateOverGroupAggregateVar(outputVar, null);

            if (computationTemplate != null)
            {
                _isUnnested = true;
                return(computationTemplate);
            }

            Node constantDefinitionNode;

            if (constantDefinitions.TryGetValue(outputVar, out constantDefinitionNode))
            {
                _isUnnested = true;
                return(constantDefinitionNode);
            }
            return(null);
        }
Example #14
0
 public override void Visit(UnnestOp op, Node n)
 {
     VisitRelOpDefault(op, n);
     Var newVar = Map(op.Var);
     if (newVar != op.Var)
     {
         n.Op = m_command.CreateUnnestOp(newVar, op.Table);
     }
 }
 public override void Visit(UnnestOp op, Node n)
 {
     VisitDefault(n);
     GroupAggregateVarRefInfo groupAggregateVarRefInfo;
     if (_groupAggregateVarInfoManager.TryGetReferencedGroupAggregateVarInfo(op.Var, out groupAggregateVarRefInfo))
     {
         PlanCompiler.Assert(op.Table.Columns.Count == 1, "Expected one column before NTE");
         _groupAggregateVarInfoManager.Add(
             op.Table.Columns[0], groupAggregateVarRefInfo.GroupAggregateVarInfo, groupAggregateVarRefInfo.Computation, true);
     }
 }
Example #16
0
 public override System.Data.Entity.Core.Query.InternalTrees.Node Visit(UnnestOp op, System.Data.Entity.Core.Query.InternalTrees.Node n)
 {
     this.AddReference(op.Var);
     this.VisitChildren(n);
     return(n);
 }
 /// <summary>
 ///     Visitor pattern method for UnnestOp
 /// </summary>
 /// <param name="op"> The UnnestOp being visited </param>
 /// <param name="n"> The Node that references the Op </param>
 public virtual void Visit(UnnestOp op, Node n)
 {
     VisitRelOpDefault(op, n);
 }
 /// <summary>
 ///     UnnestOp
 ///     Marks the unnestVar as referenced, and if there
 ///     is a child, visits the child.
 /// </summary>
 /// <param name="op"> the unnestOp </param>
 /// <param name="n"> current subtree </param>
 /// <returns> modified subtree </returns>
 public override Node Visit(UnnestOp op, Node n)
 {
     AddReference(op.Var);
     VisitChildren(n); // visit my vardefop - defining the unnest var(if any)
     return(n);
 }
Example #19
0
 public override void Visit(UnnestOp op, System.Data.Entity.Core.Query.InternalTrees.Node n)
 {
     this.VisitChildren(n);
 }
Example #20
0
        public override Node Visit(UnnestOp op, Node n)
        {
#if DEBUG
            var input = Dump.ToXml(n);
#endif
            //DEBUG
            // First, visit my children
            VisitChildren(n);

            // If we're unnesting a UDT, then simply return - we cannot eliminate this unnest
            // It must be handled by the store
            var collType = TypeHelpers.GetEdmType<md.CollectionType>(op.Var.Type);

            // Find the VarDef node for the var we're supposed to unnest.
            PlanCompiler.Assert(n.Child0.Op.OpType == OpType.VarDef, "Un-nest without VarDef input?");
            PlanCompiler.Assert(((VarDefOp)n.Child0.Op).Var == op.Var, "Un-nest var not found?");
            PlanCompiler.Assert(n.Child0.HasChild0, "VarDef without input?");
            var newNode = n.Child0.Child0;

            if (OpType.Function
                == newNode.Op.OpType)
            {
                // If we have an unnest over a function, there's nothing more we can do
                // This really means that the underlying store has the ability to
                // support TVFs, and therefore unnests, and we simply leave it as is
                return n;
            }
            else if (OpType.Collect
                     == newNode.Op.OpType)
            {
                // UnnestOp(VarDef(CollectOp(PhysicalProjectOp(x)))) ==> x

                PlanCompiler.Assert(newNode.HasChild0, "collect without input?");
                newNode = newNode.Child0;

                PlanCompiler.Assert(newNode.Op.OpType == OpType.PhysicalProject, "collect without physicalProject?");

                // Ensure others that reference my var will know to use me;
                m_definingNodeMap.Add(op.Var, newNode);
            }
            else if (OpType.VarRef
                     == newNode.Op.OpType)
            {
                // UnnestOp(VarDef(VarRef(v))) ==> copy-of-defining-node-for-v
                //
                // The Unnest's input is a VarRef; we need to replace it with
                // the defining node, and ensure we fixup the vars.

                var refVar = ((VarRefOp)newNode.Op).Var;
                Node refVarDefiningNode;
                var found = m_definingNodeMap.TryGetValue(refVar, out refVarDefiningNode);
                PlanCompiler.Assert(found, "Could not find a definition for a referenced collection var");

                newNode = CopyCollectionVarDefinition(refVarDefiningNode);

                PlanCompiler.Assert(newNode.Op.OpType == OpType.PhysicalProject, "driving node is not physicalProject?");
            }
            else
            {
                throw EntityUtil.InternalError(EntityUtil.InternalErrorCode.InvalidInternalTree, 2, newNode.Op.OpType);
            }

            IEnumerable<Var> inputVars = ((PhysicalProjectOp)newNode.Op).Outputs;

            PlanCompiler.Assert(newNode.HasChild0, "physicalProject without input?");
            newNode = newNode.Child0;

            // Dev10 #530752 : it is not correct to just remove the sort key    
            if (newNode.Op.OpType
                == OpType.Sort)
            {
                m_foundSortUnderUnnest = true;
            }

            // Update the replacement vars to reflect the pulled up operation
            UpdateReplacementVarMap(op.Table.Columns, inputVars);

#if DEBUG
            var size = input.Length; // GC.KeepAlive makes FxCop Grumpy.
            var output = Dump.ToXml(newNode);
#endif
            //DEBUG
            return newNode;
        }
Example #21
0
        // <summary>
        // Clone an UnnestOp
        // </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(UnnestOp op, Node n)
        {
            // Visit the Node's children and map their Vars
            var children = ProcessChildren(n);

            // Get the mapped unnest-var
            var mappedVar = GetMappedVar(op.Var);

            // Create a new unnestOp
            var newTable = m_destCmd.CreateTableInstance(op.Table.TableMetadata);
            var newUnnest = m_destCmd.CreateUnnestOp(mappedVar, newTable);

            // Map the corresponding tables/columns
            MapTable(newUnnest.Table, op.Table);

            // create the unnest node
            return m_destCmd.CreateNode(newUnnest, children);
        }
 /// <summary>
 /// UnnestOp handling
 /// </summary>
 /// <param name="op"></param>
 /// <param name="n"></param>
 public override void Visit(UnnestOp op, Node n)
 {
     VisitChildren(n);
 }
 /// <summary>
 ///     UnnestOp handling
 /// </summary>
 /// <param name="op"> </param>
 /// <param name="n"> </param>
 public override void Visit(UnnestOp op, Node n)
 {
     VisitChildren(n);
 }