/* * JBoss, Home of Professional Open Source. * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. Some portions may be licensed * to Red Hat, Inc. under one or more contributor license agreements. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ package org.teiid.query.optimizer.relational.rules; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.teiid.api.exception.query.QueryMetadataException; import org.teiid.api.exception.query.QueryPlannerException; import org.teiid.core.TeiidComponentException; import org.teiid.metadata.FunctionMethod.PushDown; import org.teiid.query.QueryPlugin; import org.teiid.query.analysis.AnalysisRecord; import org.teiid.query.metadata.QueryMetadataInterface; import org.teiid.query.metadata.TempMetadataID; import org.teiid.query.optimizer.capabilities.CapabilitiesFinder; import org.teiid.query.optimizer.capabilities.SourceCapabilities.Capability; import org.teiid.query.optimizer.relational.OptimizerRule; import org.teiid.query.optimizer.relational.RuleStack; import org.teiid.query.optimizer.relational.plantree.NodeConstants; import org.teiid.query.optimizer.relational.plantree.NodeConstants.Info; import org.teiid.query.optimizer.relational.plantree.NodeEditor; import org.teiid.query.optimizer.relational.plantree.NodeFactory; import org.teiid.query.optimizer.relational.plantree.PlanNode; import org.teiid.query.processor.ProcessorPlan; import org.teiid.query.processor.relational.RelationalNode; import org.teiid.query.processor.relational.RelationalNodeUtil; import org.teiid.query.resolver.util.ResolverUtil; import org.teiid.query.sql.lang.Command; import org.teiid.query.sql.lang.Criteria; import org.teiid.query.sql.lang.OrderBy; import org.teiid.query.sql.lang.OrderByItem; import org.teiid.query.sql.lang.StoredProcedure; import org.teiid.query.sql.symbol.AggregateSymbol; import org.teiid.query.sql.symbol.AliasSymbol; import org.teiid.query.sql.symbol.Constant; import org.teiid.query.sql.symbol.ElementSymbol; import org.teiid.query.sql.symbol.Expression; import org.teiid.query.sql.symbol.ExpressionSymbol; import org.teiid.query.sql.symbol.Function; import org.teiid.query.sql.symbol.GroupSymbol; import org.teiid.query.sql.symbol.WindowFunction; import org.teiid.query.sql.util.SymbolMap; import org.teiid.query.sql.visitor.AggregateSymbolCollectorVisitor; import org.teiid.query.sql.visitor.ElementCollectorVisitor; import org.teiid.query.sql.visitor.EvaluatableVisitor; import org.teiid.query.sql.visitor.ExpressionMappingVisitor; import org.teiid.query.sql.visitor.FunctionCollectorVisitor; import org.teiid.query.sql.visitor.GroupsUsedByElementsVisitor; import org.teiid.query.util.CommandContext; /** * <p>This rule is responsible for assigning the output elements to every node in the * plan. The output elements define the columns that are returned from every node. * This is generally done by figuring out top-down all the elements required to * execute the operation at each node and making sure those elements are selected * from the children nodes. </p> */ public final class RuleAssignOutputElements implements OptimizerRule { private boolean finalRun; private boolean checkSymbols; public RuleAssignOutputElements(boolean finalRun) { this.finalRun = finalRun; } /** * Execute the rule. This rule is executed exactly once during every planning * call. The plan is modified in place - only properties are manipulated, structure * is unchanged. * @param plan The plan to execute rule on * @param metadata The metadata interface * @param rules The rule stack, not modified * @return The updated plan */ public PlanNode execute(PlanNode plan, QueryMetadataInterface metadata, CapabilitiesFinder capFinder, RuleStack rules, AnalysisRecord analysisRecord, CommandContext context) throws QueryPlannerException, QueryMetadataException, TeiidComponentException { // Record project node output columns in top node PlanNode projectNode = NodeEditor.findNodePreOrder(plan, NodeConstants.Types.PROJECT); if(projectNode == null) { return plan; } List<Expression> projectCols = (List<Expression>)projectNode.getProperty(NodeConstants.Info.PROJECT_COLS); assignOutputElements(plan, projectCols, metadata, capFinder, rules, analysisRecord, context); return plan; } /** * <p>Assign the output elements at a particular node and recurse the tree. The * outputElements needed from above the node have been collected in * outputElements.</p> * * <p>SOURCE nodes: If we find a SOURCE node, this must define the top * of a virtual group. Physical groups can be identified by ACCESS nodes * at this point in the planning stage. So, we filter the virtual elements * in the virtual source based on the required output elements.</p> * * <p>SET_OP nodes: If we hit a SET_OP node, this must be a union. Unions * require a lot of special care. Unions have many branches and the projected * elements in each branch are "equivalent" in terms of nodes above the union. * This means that any filtering must occur in an identical way in all branches * of a union.</p> * * @param root Node to assign * @param outputElements Output elements needed for this node * @param metadata Metadata implementation */ private void assignOutputElements(PlanNode root, List<Expression> outputElements, QueryMetadataInterface metadata, CapabilitiesFinder capFinder, RuleStack rules, AnalysisRecord analysisRecord, CommandContext context) throws QueryPlannerException, QueryMetadataException, TeiidComponentException { int nodeType = root.getType(); // Update this node's output columns based on parent's columns List<Expression> oldOutput = (List<Expression>) root.setProperty(NodeConstants.Info.OUTPUT_COLS, outputElements); if (root.getChildCount() == 0) { //update temp access if (root.getType() == NodeConstants.Types.SOURCE && root.getGroups().size() == 1) { GroupSymbol gs = root.getGroups().iterator().next(); if (gs.getMetadataID() instanceof TempMetadataID) { for (Expression ex : outputElements) { if (ex instanceof ElementSymbol) { Object id = ((ElementSymbol)ex).getMetadataID(); if (id instanceof TempMetadataID) { ((TempMetadataID) id).setAccessed(true); } } } } } return; } switch (nodeType) { case NodeConstants.Types.ACCESS: Command command = FrameUtil.getNonQueryCommand(root); if (command instanceof StoredProcedure) { //if the access node represents a stored procedure, then we can't actually change the output symbols root.setProperty(NodeConstants.Info.OUTPUT_COLS, command.getProjectedSymbols()); } else { ProcessorPlan plan = FrameUtil.getNestedPlan(root); if (plan != null && (command == null || !RelationalNodeUtil.isUpdate(command))) { //nested with clauses are handled as sub plans, which have a fixed set of output symbols root.setProperty(NodeConstants.Info.OUTPUT_COLS, ResolverUtil.resolveElementsInGroup(root.getGroups().iterator().next(), metadata)); } if (checkSymbols) { Object modelId = RuleRaiseAccess.getModelIDFromAccess(root, metadata); for (Expression symbol : outputElements) { if(!RuleRaiseAccess.canPushSymbol(symbol, true, modelId, metadata, capFinder, analysisRecord)) { throw new QueryPlannerException(QueryPlugin.Event.TEIID30258, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30258, symbol, modelId)); } } } if (NodeEditor.findParent(root, NodeConstants.Types.PROJECT, NodeConstants.Types.SOURCE) != null) { //there's a chance that partial projection was used. we are not a defacto project node //take credit for creating anything that is not an element symbol LinkedHashSet<Expression> filteredElements = new LinkedHashSet<Expression>(); for (Expression element : outputElements) { if(element instanceof ElementSymbol) { filteredElements.add(element); } else { filteredElements.addAll(ElementCollectorVisitor.getElements(element, false)); } } outputElements = new ArrayList<Expression>(filteredElements); } } assignOutputElements(root.getLastChild(), outputElements, metadata, capFinder, rules, analysisRecord, context); break; case NodeConstants.Types.DUP_REMOVE: //targeted optimization based upon swapping the dup remove for a limit 1 //TODO: may need to also check for grouping over constants boolean allConstants = true; for (Expression ex : outputElements) { if (!(EvaluatableVisitor.willBecomeConstant(SymbolMap.getExpression(ex)))) { allConstants = false; break; } } if (allConstants && addLimit(rules, root, metadata, capFinder)) { //TODO we could more gracefully handle the !addLimit case PlanNode parent = root.getParent(); if (parent != null) { NodeEditor.removeChildNode(root.getParent(), root); execute(parent, metadata, capFinder, rules, analysisRecord, context); return; } } case NodeConstants.Types.SORT: //correct expression positions and update the unrelated flag OrderBy order = (OrderBy) root.getProperty(NodeConstants.Info.SORT_ORDER); if (order != null && (oldOutput == null || !oldOutput.equals(outputElements))) { outputElements = new ArrayList<Expression>(outputElements); boolean hasUnrelated = false; for (OrderByItem item : order.getOrderByItems()) { int index = outputElements.indexOf(item.getSymbol()); if (index != -1) { item.setExpressionPosition(index); } else { hasUnrelated = true; outputElements.add(item.getSymbol()); } } if (!hasUnrelated) { root.setProperty(NodeConstants.Info.UNRELATED_SORT, false); } else { root.setProperty(NodeConstants.Info.UNRELATED_SORT, true); } } assignOutputElements(root.getLastChild(), outputElements, metadata, capFinder, rules, analysisRecord, context); break; case NodeConstants.Types.TUPLE_LIMIT: assignOutputElements(root.getLastChild(), outputElements, metadata, capFinder, rules, analysisRecord, context); break; case NodeConstants.Types.SOURCE: { outputElements = (List<Expression>)determineSourceOutput(root, outputElements, metadata, capFinder); root.setProperty(NodeConstants.Info.OUTPUT_COLS, outputElements); List<Expression> childElements = filterVirtualElements(root, outputElements, metadata); assignOutputElements(root.getFirstChild(), childElements, metadata, capFinder, rules, analysisRecord, context); break; } case NodeConstants.Types.SET_OP: { for (PlanNode childNode : root.getChildren()) { PlanNode projectNode = NodeEditor.findNodePreOrder(childNode, NodeConstants.Types.PROJECT); List<Expression> projectCols = (List<Expression>)projectNode.getProperty(NodeConstants.Info.PROJECT_COLS); assignOutputElements(childNode, projectCols, metadata, capFinder, rules, analysisRecord, context); } break; } default: { PlanNode sortNode = null; if (root.getType() == NodeConstants.Types.PROJECT) { GroupSymbol intoGroup = (GroupSymbol)root.getProperty(NodeConstants.Info.INTO_GROUP); if (intoGroup != null) { //if this is a project into, treat the nodes under the source as a new plan root PlanNode intoRoot = NodeEditor.findNodePreOrder(root, NodeConstants.Types.SOURCE); execute(intoRoot.getFirstChild(), metadata, capFinder, rules, analysisRecord, context); return; } List<Expression> projectCols = outputElements; sortNode = NodeEditor.findParent(root, NodeConstants.Types.SORT, NodeConstants.Types.SOURCE); if (finalRun && sortNode != null && sortNode.hasBooleanProperty(NodeConstants.Info.UNRELATED_SORT)) { root.getGroups().clear(); root.addGroups(GroupsUsedByElementsVisitor.getGroups(projectCols)); root.addGroups(GroupsUsedByElementsVisitor.getGroups(root.getCorrelatedReferenceElements())); } root.setProperty(NodeConstants.Info.PROJECT_COLS, projectCols); if (root.hasBooleanProperty(Info.HAS_WINDOW_FUNCTIONS)) { Set<WindowFunction> windowFunctions = getWindowFunctions(projectCols); if (windowFunctions.isEmpty()) { root.setProperty(Info.HAS_WINDOW_FUNCTIONS, false); } } } List<Expression> requiredInput = collectRequiredInputSymbols(root, metadata, capFinder); //targeted optimization for unnecessary aggregation if (root.getType() == NodeConstants.Types.GROUP && root.hasBooleanProperty(Info.IS_OPTIONAL) && NodeEditor.findParent(root, NodeConstants.Types.ACCESS) == null) { PlanNode parent = removeGroupBy(root, metadata); if (!root.hasCollectionProperty(Info.GROUP_COLS)) { //just lob off everything under the projection PlanNode project = NodeEditor.findNodePreOrder(parent, NodeConstants.Types.PROJECT); project.removeAllChildren(); } else if (!addLimit(rules, parent, metadata, capFinder)) { throw new AssertionError("expected limit node to be added"); //$NON-NLS-1$ } execute(parent, metadata, capFinder, rules, analysisRecord, context); return; } // Call children recursively if(root.getChildCount() == 1) { assignOutputElements(root.getLastChild(), requiredInput, metadata, capFinder, rules, analysisRecord, context); if (!finalRun && root.getType() == NodeConstants.Types.PROJECT && sortNode != null && sortNode.hasBooleanProperty(NodeConstants.Info.UNRELATED_SORT)) { //if this is the initial rule run, remove unrelated order to preserve the original projection OrderBy elements = (OrderBy) sortNode.getProperty(NodeConstants.Info.SORT_ORDER); outputElements = new ArrayList<Expression>(outputElements); for (OrderByItem item : elements.getOrderByItems()) { if (item.getExpressionPosition() == -1) { outputElements.remove(item.getSymbol()); } } root.setProperty(NodeConstants.Info.PROJECT_COLS, outputElements); } } else { //determine which elements go to each side of the join for (PlanNode childNode : root.getChildren()) { Set<GroupSymbol> filterGroups = FrameUtil.findJoinSourceNode(childNode).getGroups(); List<Expression> filteredElements = filterElements(requiredInput, filterGroups); // Call child recursively assignOutputElements(childNode, filteredElements, metadata, capFinder, rules, analysisRecord, context); } } } } } private boolean addLimit(RuleStack rules, PlanNode parent, QueryMetadataInterface metadata, CapabilitiesFinder capabilitiesFinder) throws QueryMetadataException, TeiidComponentException { PlanNode accessNode = NodeEditor.findParent(parent.getFirstChild(), NodeConstants.Types.ACCESS); if (accessNode != null) { Object mid = RuleRaiseAccess.getModelIDFromAccess(accessNode, metadata); if (!CapabilitiesUtil.supports(Capability.ROW_LIMIT, mid, metadata, capabilitiesFinder)) { if (NodeEditor.findParent(parent, NodeConstants.Types.SET_OP | NodeConstants.Types.JOIN, NodeConstants.Types.ACCESS) != null) { return false; //access node is too high } parent = accessNode; } } PlanNode limit = NodeFactory.getNewNode(NodeConstants.Types.TUPLE_LIMIT); limit.setProperty(Info.MAX_TUPLE_LIMIT, new Constant(1)); if (!rules.contains(RuleConstants.PUSH_LIMIT)) { rules.push(RuleConstants.PUSH_LIMIT); } if (parent.getParent() == null) { if (parent.getType() == NodeConstants.Types.ACCESS) { return false; } parent = parent.getFirstChild(); } parent.addAsParent(limit); return true; } static PlanNode removeGroupBy(PlanNode root, QueryMetadataInterface metadata) throws QueryPlannerException { PlanNode next = root.getFirstChild(); NodeEditor.removeChildNode(root.getParent(), root); SymbolMap symbolMap = (SymbolMap) root.getProperty(NodeConstants.Info.SYMBOL_MAP); if (!symbolMap.asMap().isEmpty()) { FrameUtil.convertFrame(next.getParent(), symbolMap.asMap().keySet().iterator().next().getGroupSymbol(), null, symbolMap.asMap(), metadata); } PlanNode parent = next.getParent(); while (parent.getParent() != null && parent.getParent().getType() != NodeConstants.Types.SOURCE && parent.getParent().getType() != NodeConstants.Types.SET_OP) { parent = parent.getParent(); } return parent; } public static Set<WindowFunction> getWindowFunctions( List<Expression> projectCols) { LinkedHashSet<WindowFunction> windowFunctions = new LinkedHashSet<WindowFunction>(); for (Expression singleElementSymbol : projectCols) { AggregateSymbolCollectorVisitor.getAggregates(singleElementSymbol, null, null, null, windowFunctions, null); } return windowFunctions; } private List<Expression> filterElements(Collection<? extends Expression> requiredInput, Set<GroupSymbol> filterGroups) { List<Expression> filteredElements = new ArrayList<Expression>(); for (Expression element : requiredInput) { if(filterGroups.containsAll(GroupsUsedByElementsVisitor.getGroups(element))) { filteredElements.add(element); } } return filteredElements; } /** * A special case to consider is when the virtual group is defined by a * UNION (no ALL) or a SELECT DISTINCT. In this case, the dup removal means * that all columns need to be used to determine duplicates. So, filtering the * columns at all will alter the number of rows flowing through the frame. * So, in this case filtering should not occur. In fact the output columns * that were set on root above are filtered, but we actually want all the * virtual elements - so just reset it and proceed as before * @throws TeiidComponentException * @throws QueryMetadataException * @throws QueryPlannerException */ static List<? extends Expression> determineSourceOutput(PlanNode root, List<Expression> outputElements, QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException, QueryPlannerException { PlanNode virtualRoot = root.getLastChild(); if(hasDupRemoval(virtualRoot)) { // Reset the outputColumns for this source node to be all columns for the virtual group SymbolMap symbolMap = (SymbolMap) root.getProperty(NodeConstants.Info.SYMBOL_MAP); if (!symbolMap.asMap().keySet().containsAll(outputElements)) { outputElements.removeAll(symbolMap.asMap().keySet()); throw new QueryPlannerException(QueryPlugin.Event.TEIID30259, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30259, outputElements)); } return symbolMap.getKeys(); } PlanNode limit = NodeEditor.findNodePreOrder(root, NodeConstants.Types.TUPLE_LIMIT, NodeConstants.Types.PROJECT); if (limit == null) { return outputElements; } //reset the output elements to be the output columns + what's required by the sort PlanNode sort = NodeEditor.findNodePreOrder(limit, NodeConstants.Types.SORT, NodeConstants.Types.PROJECT); if (sort == null) { return outputElements; } PlanNode access = NodeEditor.findParent(sort, NodeConstants.Types.ACCESS); if (sort.hasBooleanProperty(NodeConstants.Info.UNRELATED_SORT) || (access != null && capFinder != null && CapabilitiesUtil.supports(Capability.QUERY_ORDERBY_UNRELATED, RuleRaiseAccess.getModelIDFromAccess(access, metadata), metadata, capFinder))) { return outputElements; } OrderBy sortOrder = (OrderBy)sort.getProperty(NodeConstants.Info.SORT_ORDER); List<Expression> topCols = FrameUtil.findTopCols(sort); SymbolMap symbolMap = (SymbolMap)root.getProperty(NodeConstants.Info.SYMBOL_MAP); List<ElementSymbol> symbolOrder = symbolMap.getKeys(); for (OrderByItem item : sortOrder.getOrderByItems()) { final Expression expr = item.getSymbol(); int index = topCols.indexOf(expr); if (index < 0) { continue; } ElementSymbol symbol = symbolOrder.get(index); if (!outputElements.contains(symbol)) { outputElements.add(symbol); } } return outputElements; } /** * <p>This method looks at a source node, which defines a virtual group, and filters the * virtual elements defined by the group down into just the output elements needed * by that source node. This means, for instance, that the PROJECT node at the top * of the virtual group might need to have some elements removed from the project as * those elements are no longer needed. </p> * * <p>One special case that is handled here is when a virtual group is defined by * a UNION ALL. In this case, the various branches of the union have elements defined * and filtering must occur identically in all branches of the union. </p> * * @param sourceNode Node to filter * @param metadata Metadata implementation * @return The filtered list of columns for this node (used in recursing tree) * @throws QueryPlannerException */ static List<Expression> filterVirtualElements(PlanNode sourceNode, List<Expression> outputColumns, QueryMetadataInterface metadata) throws QueryPlannerException { PlanNode virtualRoot = sourceNode.getLastChild(); // Update project cols - typically there is exactly one and that node can // just get the filteredCols determined above. In the case of one or more // nested set operations (UNION, INTERSECT, EXCEPT) there will be 2 or more // projects. List<PlanNode> allProjects = NodeEditor.findAllNodes(virtualRoot, NodeConstants.Types.PROJECT, NodeConstants.Types.PROJECT); int[] filteredIndex = new int[outputColumns.size()]; Arrays.fill(filteredIndex, -1); SymbolMap symbolMap = (SymbolMap)sourceNode.getProperty(NodeConstants.Info.SYMBOL_MAP); List<ElementSymbol> originalOrder = symbolMap.getKeys(); boolean updateGroups = outputColumns.size() != originalOrder.size(); boolean[] seenIndex = new boolean[outputColumns.size()]; boolean newSymbols = false; int newSymbolIndex = 0; for (int i = 0; i < outputColumns.size(); i++) { Expression expr = outputColumns.get(i); filteredIndex[i] = originalOrder.indexOf(expr); if (filteredIndex[i] == -1) { updateGroups = true; //we're adding this symbol, which needs to be updated against respective symbol maps newSymbols = true; } else { newSymbolIndex++; } if (!updateGroups) { seenIndex[filteredIndex[i]] = true; } } if (!updateGroups) { for (boolean b : seenIndex) { if (!b) { updateGroups = true; break; } } } List<Expression> newCols = null; for(int i=allProjects.size()-1; i>=0; i--) { PlanNode projectNode = allProjects.get(i); List<Expression> projectCols = (List<Expression>) projectNode.getProperty(NodeConstants.Info.PROJECT_COLS); newCols = RelationalNode.projectTuple(filteredIndex, projectCols, true); if (newSymbols) { SymbolMap childMap = SymbolMap.createSymbolMap(symbolMap.getKeys(), projectCols); for (int j = 0; j < filteredIndex.length; j++) { if (filteredIndex[j] != -1) { continue; } Expression ex = (Expression) outputColumns.get(j).clone(); ExpressionMappingVisitor.mapExpressions(ex, childMap.asMap()); newCols.set(j, ex); if (i == 0) { filteredIndex[j] = newSymbolIndex++; } } } projectNode.setProperty(NodeConstants.Info.PROJECT_COLS, newCols); if (updateGroups) { projectNode.getGroups().clear(); projectNode.addGroups(GroupsUsedByElementsVisitor.getGroups(newCols)); projectNode.addGroups(GroupsUsedByElementsVisitor.getGroups(projectNode.getCorrelatedReferenceElements())); } } if (!updateGroups) { for (int i : filteredIndex) { if (i != filteredIndex[i]) { updateGroups = true; break; } } } if (updateGroups) { SymbolMap newMap = new SymbolMap(); List<Expression> originalExpressionOrder = symbolMap.getValues(); for (int i = 0; i < filteredIndex.length; i++) { if (filteredIndex[i] < originalOrder.size()) { newMap.addMapping(originalOrder.get(filteredIndex[i]), originalExpressionOrder.get(filteredIndex[i])); } //else TODO: we may need to create a fake symbol } sourceNode.setProperty(NodeConstants.Info.SYMBOL_MAP, newMap); } // Create output columns for virtual group project return newCols; } /** * Check all branches for either a dup removal or a non all union. * * @param node Root of virtual group (node below source node) * @return True if the virtual group at this source node does dup removal */ static boolean hasDupRemoval(PlanNode node) { List<PlanNode> nodes = NodeEditor.findAllNodes(node, NodeConstants.Types.DUP_REMOVE|NodeConstants.Types.SET_OP, NodeConstants.Types.DUP_REMOVE|NodeConstants.Types.PROJECT); for (PlanNode planNode : nodes) { if (planNode.getType() == NodeConstants.Types.DUP_REMOVE || (planNode.getType() == NodeConstants.Types.SET_OP && Boolean.FALSE.equals(planNode.getProperty(NodeConstants.Info.USE_ALL)))) { return true; } } return false; } /** * Collect all required input symbols for a given node. Input symbols * are any symbols that are required in the processing of this node, * for instance to create a new element symbol or sort on it, etc. * @param node Node to collect for * @param metadata * @param capFinder * @throws TeiidComponentException * @throws QueryMetadataException */ private List<Expression> collectRequiredInputSymbols(PlanNode node, QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException { Set<Expression> requiredSymbols = new LinkedHashSet<Expression>(); Set<Expression> createdSymbols = new HashSet<Expression>(); List<Expression> outputCols = (List<Expression>) node.getProperty(NodeConstants.Info.OUTPUT_COLS); switch(node.getType()) { case NodeConstants.Types.PROJECT: { List<Expression> projectCols = (List<Expression>) node.getProperty(NodeConstants.Info.PROJECT_COLS); PlanNode accessParent = NodeEditor.findParent(node, NodeConstants.Types.ACCESS); PlanNode accessNode = null; if (accessParent == null) { //find the direct access node accessNode = NodeEditor.findNodePreOrder(node, NodeConstants.Types.ACCESS, NodeConstants.Types.SOURCE | NodeConstants.Types.JOIN | NodeConstants.Types.SET_OP | NodeConstants.Types.GROUP); } for (Expression ss : projectCols) { if(ss instanceof AliasSymbol) { createdSymbols.add(ss); ss = ((AliasSymbol)ss).getSymbol(); } if (ss instanceof WindowFunction || ss instanceof ExpressionSymbol) { createdSymbols.add(ss); } if (!pushProjection(node, metadata, capFinder, requiredSymbols, accessParent, accessNode, ss)) { ElementCollectorVisitor.getElements(ss, requiredSymbols); } } break; } case NodeConstants.Types.SELECT: Criteria selectCriteria = (Criteria) node.getProperty(NodeConstants.Info.SELECT_CRITERIA); ElementCollectorVisitor.getElements(selectCriteria, requiredSymbols); break; case NodeConstants.Types.JOIN: List<Criteria> crits = (List) node.getProperty(NodeConstants.Info.JOIN_CRITERIA); if(crits != null) { for (Criteria joinCriteria : crits) { ElementCollectorVisitor.getElements(joinCriteria, requiredSymbols); } } break; case NodeConstants.Types.GROUP: List<Expression> groupCols = (List<Expression>) node.getProperty(NodeConstants.Info.GROUP_COLS); PlanNode accessParent = NodeEditor.findParent(node, NodeConstants.Types.ACCESS); PlanNode accessNode = null; if (accessParent == null) { //find the direct access node accessNode = NodeEditor.findNodePreOrder(node.getFirstChild(), NodeConstants.Types.ACCESS, NodeConstants.Types.SOURCE | NodeConstants.Types.JOIN | NodeConstants.Types.SET_OP | NodeConstants.Types.GROUP); } if(groupCols != null) { for (Expression expression : groupCols) { if (!pushProjection(node, metadata, capFinder, requiredSymbols, accessParent, accessNode, expression)) { ElementCollectorVisitor.getElements(expression, requiredSymbols); } } } SymbolMap symbolMap = (SymbolMap) node.getProperty(NodeConstants.Info.SYMBOL_MAP); Set<ElementSymbol> usedAggregates = new HashSet<ElementSymbol>(); // Take credit for creating any aggregates that are needed above for (Expression outputSymbol : outputCols) { if (!(outputSymbol instanceof ElementSymbol)) { continue; } createdSymbols.add(outputSymbol); Expression ex = symbolMap.getMappedExpression((ElementSymbol) outputSymbol); if(ex instanceof AggregateSymbol) { AggregateSymbol agg = (AggregateSymbol)ex; Expression[] aggExprs = agg.getArgs(); for (Expression expression : aggExprs) { if (!pushProjection(node, metadata, capFinder, requiredSymbols, accessParent, accessNode, expression)) { ElementCollectorVisitor.getElements(expression, requiredSymbols); } } OrderBy orderBy = agg.getOrderBy(); if(orderBy != null) { ElementCollectorVisitor.getElements(orderBy, requiredSymbols); } Expression condition = agg.getCondition(); if(condition != null) { ElementCollectorVisitor.getElements(condition, requiredSymbols); } usedAggregates.add((ElementSymbol) outputSymbol); } } //update the aggs in the symbolmap for (Map.Entry<ElementSymbol, Expression> entry : new ArrayList<Map.Entry<ElementSymbol, Expression>>(symbolMap.asMap().entrySet())) { if (entry.getValue() instanceof AggregateSymbol && !usedAggregates.contains(entry.getKey())) { symbolMap.asUpdatableMap().remove(entry.getKey()); } } if (requiredSymbols.isEmpty() && usedAggregates.isEmpty()) { node.setProperty(Info.IS_OPTIONAL, true); } break; } // Gather elements from correlated subquery references; for (SymbolMap refs : node.getAllReferences()) { for (Expression expr : refs.asMap().values()) { ElementCollectorVisitor.getElements(expr, requiredSymbols); } } /* Set<SingleElementSymbol> tempRequired = requiredSymbols; requiredSymbols = new LinkedHashSet<SingleElementSymbol>(outputCols); requiredSymbols.removeAll(createdSymbols); requiredSymbols.addAll(tempRequired); */ // Add any columns to required that are in this node's output but were not created here for (Expression currentOutputSymbol : outputCols) { if (!createdSymbols.contains(currentOutputSymbol) && (finalRun || node.getType() != NodeConstants.Types.PROJECT || currentOutputSymbol instanceof ElementSymbol)) { requiredSymbols.add(currentOutputSymbol); } } //further minimize the required symbols based upon underlying expression (accounts for aliasing) //TODO: this should depend upon whether the expressions are deterministic if (node.getType() == NodeConstants.Types.PROJECT) { Set<Expression> expressions = new HashSet<Expression>(); for (Iterator<Expression> iterator = requiredSymbols.iterator(); iterator.hasNext();) { Expression ses = iterator.next(); if (!expressions.add(SymbolMap.getExpression(ses))) { iterator.remove(); } } } return new ArrayList<Expression>(requiredSymbols); } private boolean pushProjection(PlanNode node, QueryMetadataInterface metadata, CapabilitiesFinder capFinder, Set<Expression> requiredSymbols, PlanNode accessParent, PlanNode accessNode, Expression ss) throws QueryMetadataException, TeiidComponentException { if (finalRun && accessParent == null) { Expression ex = SymbolMap.getExpression(ss); if (ex instanceof ElementSymbol || ex instanceof Constant) { return false; } Object modelId = null; if (accessNode != null) { modelId = RuleRaiseAccess.getModelIDFromAccess(accessNode, metadata); //narrow check for projection pushing if (RuleRaiseAccess.canPushSymbol(ss, true, modelId, metadata, capFinder, null)) { requiredSymbols.add(ss); return true; } } if (NodeEditor.findNodePreOrder(node, NodeConstants.Types.GROUP, NodeConstants.Types.ACCESS) == null) { Collection<Function> functions = FunctionCollectorVisitor.getFunctions(ss, false); List<Function> mustPushSubexpression = null; for (Function function : functions) { if (function.getFunctionDescriptor().getPushdown() != PushDown.MUST_PUSHDOWN || (EvaluatableVisitor.willBecomeConstant(function) && accessNode != null && CapabilitiesUtil.supports(Capability.SELECT_WITHOUT_FROM, modelId, metadata, capFinder))) { continue; } //there is a special check in the evaluator for a must pushdown function to use //the projected value //TODO: we could try to get something in between everything and just the partial if (accessNode != null && RuleRaiseAccess.canPushSymbol(function, true, modelId, metadata, capFinder, null)) { if (mustPushSubexpression == null) { mustPushSubexpression = new ArrayList<Function>(); } mustPushSubexpression.add(function); continue; } //assume we need the whole thing requiredSymbols.add(ss); checkSymbols = true; return true; } if (mustPushSubexpression != null) { requiredSymbols.addAll(mustPushSubexpression); } } } return false; } /** * Get name of the rule * @return Name of the rule */ public String toString() { return "AssignOutputElements"; //$NON-NLS-1$ } }