/* * 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.plantree; import java.util.*; import org.teiid.api.exception.query.QueryMetadataException; import org.teiid.client.plan.Annotation; import org.teiid.client.plan.Annotation.Priority; import org.teiid.core.TeiidComponentException; import org.teiid.query.analysis.AnalysisRecord; import org.teiid.query.metadata.QueryMetadataInterface; import org.teiid.query.optimizer.relational.plantree.NodeConstants.Info; import org.teiid.query.sql.LanguageObject; import org.teiid.query.sql.lang.Criteria; import org.teiid.query.sql.lang.OrderBy; import org.teiid.query.sql.lang.SubqueryContainer; import org.teiid.query.sql.lang.TableFunctionReference; import org.teiid.query.sql.symbol.ElementSymbol; import org.teiid.query.sql.symbol.Expression; import org.teiid.query.sql.symbol.GroupSymbol; import org.teiid.query.sql.util.SymbolMap; import org.teiid.query.sql.visitor.ElementCollectorVisitor; import org.teiid.query.sql.visitor.GroupsUsedByElementsVisitor; import org.teiid.query.sql.visitor.ValueIteratorProviderCollectorVisitor; public class PlanNode { // --------------------- Node State -------------------------- /** The type of node, as defined by NodeConstants.Types. */ private int type; private boolean modified; /** The parent of this node, null if root. */ private PlanNode parent; /** Child nodes, usually just 1 or 2, but occasionally more */ private LinkedList<PlanNode> children = new LinkedList<PlanNode>(); private List<PlanNode> childrenView = Collections.unmodifiableList(children); /** Type-specific node properties, as defined in NodeConstants.Info. */ private Map<NodeConstants.Info, Object> nodeProperties; // --------------------- Planning Info -------------------------- /** The set of groups that this node deals with. */ private Set<GroupSymbol> groups = new LinkedHashSet<GroupSymbol>(); // ========================================================================= // C O N S T R U C T O R S // ========================================================================= public PlanNode() { } // ========================================================================= // A C C E S S O R M E T H O D S // ========================================================================= public int getType() { return type; } public void setType(int type) { this.type = type; } public PlanNode getParent() { return parent; } private void setParent(PlanNode parent) { if (this.parent != null) { this.parent.children.remove(this); } this.modified = true; this.parent = parent; } public List<PlanNode> getChildren() { return this.childrenView; } public List<PlanNode> removeAllChildren() { ArrayList<PlanNode> childrenCopy = new ArrayList<PlanNode>(children); for (Iterator<PlanNode> childIter = this.children.iterator(); childIter.hasNext();) { PlanNode child = childIter.next(); childIter.remove(); child.parent = null; } this.modified = true; return childrenCopy; } public int getChildCount() { return this.children.size(); } public PlanNode getFirstChild() { if ( getChildCount() > 0 ) { return this.children.getFirst(); } return null; } public PlanNode getLastChild() { if ( getChildCount() > 0 ) { return this.children.getLast(); } return null; } public void addFirstChild(PlanNode child) { this.modified = true; this.children.addFirst(child); child.setParent(this); } public void addLastChild(PlanNode child) { this.modified = true; this.children.addLast(child); child.setParent(this); } public void addChildren(Collection<PlanNode> otherChildren) { for (PlanNode planNode : otherChildren) { this.addLastChild(planNode); } } public PlanNode removeFromParent() { this.modified = true; PlanNode result = this.parent; if (result != null) { result.removeChild(this); } return result; } public boolean removeChild(PlanNode child) { boolean result = this.children.remove(child); if (result) { child.parent = null; modified = true; } return result; } public Object getProperty(NodeConstants.Info propertyID) { if(nodeProperties == null) { return null; } Object result = nodeProperties.get(propertyID); if (result != null) { modified = true; //we may modify this object } return result; } public Object setProperty(NodeConstants.Info propertyID, Object value) { if(nodeProperties == null) { nodeProperties = new LinkedHashMap<NodeConstants.Info, Object>(); } modified = true; return nodeProperties.put(propertyID, value); } public Object removeProperty(Object propertyID) { if(nodeProperties == null) { return null; } modified = true; return nodeProperties.remove(propertyID); } /** * Indicates if there is a non-null value for the property * key or not * @param propertyID one of the properties from {@link NodeConstants} * @return whether this node has a non-null value for that property */ public boolean hasProperty(NodeConstants.Info propertyID) { return (getProperty(propertyID) != null); } /** * Indicates if there is a non-null and non-empty Collection value for the property * key or not * @param propertyID one of the properties from {@link NodeConstants} which is * known to be a Collection object of some sort * @return whether this node has a non-null and non-empty Collection * value for that property */ public boolean hasCollectionProperty(NodeConstants.Info propertyID) { Collection<Object> value = (Collection<Object>)getProperty(propertyID); return (value != null && !value.isEmpty()); } public void addGroup(GroupSymbol groupID) { modified = true; groups.add(groupID); } public void addGroups(Collection<GroupSymbol> newGroups) { modified = true; this.groups.addAll(newGroups); } public Set<GroupSymbol> getGroups() { return groups; } // ========================================================================= // O V E R R I D D E N O B J E C T M E T H O D S // ========================================================================= /** * Print plantree structure starting at this node * @return String representing this node and all children under this node */ public String toString() { StringBuilder str = new StringBuilder(); getRecursiveString(str, 0, null); return str.toString(); } /** * Get the single node in full or recursive which considers modifications. * @return String representing just this node */ public String nodeToString(boolean recusive) { StringBuilder str = new StringBuilder(); if (!recusive) { getNodeString(str, null); } else { getRecursiveString(str, 0, this.modified); } return str.toString(); } // Define a single tab private static final String TAB = " "; //$NON-NLS-1$ private static void setTab(StringBuilder str, int tabStop) { for(int i=0; i<tabStop; i++) { str.append(TAB); } } void getRecursiveString(StringBuilder str, int tabLevel, Boolean mod) { setTab(str, tabLevel); getNodeString(str, mod); str.append(")\n"); //$NON-NLS-1$ // Recursively add children at one greater tab level for (PlanNode child : children) { child.getRecursiveString(str, tabLevel+1, mod==null?null:child.modified); } } void getNodeString(StringBuilder str, Boolean mod) { str.append(NodeConstants.getNodeTypeString(this.type)); str.append("(groups="); //$NON-NLS-1$ str.append(this.groups); if (!Boolean.FALSE.equals(mod)) { if(nodeProperties != null) { str.append(", props="); //$NON-NLS-1$ String props = nodeProperties.toString(); if (props.length() > 100000) { props = props.substring(0, 100000) + "..."; //$NON-NLS-1$ } str.append(props); } if (Boolean.TRUE.equals(mod)) { modified = false; } } } public boolean hasBooleanProperty(NodeConstants.Info propertyKey) { return Boolean.TRUE.equals(getProperty(propertyKey)); } public void replaceChild(PlanNode child, PlanNode replacement) { modified = true; int i = this.children.indexOf(child); this.children.set(i, replacement); child.setParent(null); replacement.setParent(this); } /** * Add the node as this node's parent. NOTE: This node * must already have a parent. * @param node */ public void addAsParent(PlanNode node) { modified = true; if (this.parent != null) { this.parent.replaceChild(this, node); } assert node.getChildCount() == 0; node.addLastChild(this); } public List<SymbolMap> getCorrelatedReferences() { List<SubqueryContainer<?>> containers = getSubqueryContainers(); if (containers.isEmpty()) { return Collections.emptyList(); } ArrayList<SymbolMap> result = new ArrayList<SymbolMap>(containers.size()); for (SubqueryContainer<?> container : containers) { SymbolMap map = container.getCommand().getCorrelatedReferences(); if (map != null) { result.add(map); } } return result; } public List<SymbolMap> getAllReferences() { List<SymbolMap> refMaps = new ArrayList<SymbolMap>(getCorrelatedReferences()); refMaps.addAll(getExportedCorrelatedReferences()); return refMaps; } public List<SymbolMap> getExportedCorrelatedReferences() { if (type != NodeConstants.Types.JOIN) { return Collections.emptyList(); } LinkedList<SymbolMap> result = new LinkedList<SymbolMap>(); for (PlanNode child : NodeEditor.findAllNodes(this, NodeConstants.Types.SOURCE, NodeConstants.Types.ACCESS)) { SymbolMap references = (SymbolMap)child.getProperty(NodeConstants.Info.CORRELATED_REFERENCES); if (references == null) { continue; } Set<GroupSymbol> correlationGroups = GroupsUsedByElementsVisitor.getGroups(references.getValues()); PlanNode joinNode = NodeEditor.findParent(child, NodeConstants.Types.JOIN, NodeConstants.Types.SOURCE); while (joinNode != null) { if (joinNode.getGroups().containsAll(correlationGroups)) { if (joinNode == this) { result.add(references); } break; } joinNode = NodeEditor.findParent(joinNode, NodeConstants.Types.JOIN, NodeConstants.Types.SOURCE); } } return result; } public Set<ElementSymbol> getCorrelatedReferenceElements() { List<SymbolMap> maps = getCorrelatedReferences(); if(maps.isEmpty()) { return Collections.emptySet(); } HashSet<ElementSymbol> result = new HashSet<ElementSymbol>(); for (SymbolMap symbolMap : maps) { List<Expression> values = symbolMap.getValues(); for (Expression expr : values) { ElementCollectorVisitor.getElements(expr, result); } } return result; } public List<SubqueryContainer<?>> getSubqueryContainers() { Collection<? extends LanguageObject> toSearch = Collections.emptyList(); switch (this.getType()) { case NodeConstants.Types.SELECT: { Criteria criteria = (Criteria) this.getProperty(NodeConstants.Info.SELECT_CRITERIA); toSearch = Arrays.asList(criteria); break; } case NodeConstants.Types.PROJECT: { toSearch = (Collection) this.getProperty(NodeConstants.Info.PROJECT_COLS); break; } case NodeConstants.Types.JOIN: { toSearch = (List<Criteria>) this.getProperty(NodeConstants.Info.JOIN_CRITERIA); break; } case NodeConstants.Types.SOURCE: { TableFunctionReference tfr = (TableFunctionReference)this.getProperty(NodeConstants.Info.TABLE_FUNCTION); if (tfr != null) { toSearch = Arrays.asList(tfr); } break; } case NodeConstants.Types.GROUP: { SymbolMap groupMap = (SymbolMap)this.getProperty(Info.SYMBOL_MAP); toSearch = groupMap.getValues(); break; } case NodeConstants.Types.SORT: { OrderBy orderBy = (OrderBy) this.getProperty(NodeConstants.Info.SORT_ORDER); if (orderBy != null) { toSearch = orderBy.getOrderByItems(); } } } return ValueIteratorProviderCollectorVisitor.getValueIteratorProviders(toSearch); } public float getCardinality() { Float cardinality = (Float) this.getProperty(NodeConstants.Info.EST_CARDINALITY); if (cardinality == null) { return -1f; } return cardinality; } public void recordDebugAnnotation(String annotation, Object modelID, String resolution, AnalysisRecord record, QueryMetadataInterface metadata) throws QueryMetadataException, TeiidComponentException { if (record != null && record.recordAnnotations()) { boolean current = this.modified; this.modified = true; record.addAnnotation(Annotation.RELATIONAL_PLANNER, annotation + (modelID != null?" " + (metadata!=null?metadata.getName(modelID):modelID):""), resolution + " " + this.nodeToString(false), Priority.LOW); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ this.modified = current; } } @Override public PlanNode clone() { PlanNode node = new PlanNode(); node.type = this.type; node.groups = new HashSet<GroupSymbol>(this.groups); if (this.nodeProperties != null) { node.nodeProperties = new LinkedHashMap<NodeConstants.Info, Object>(this.nodeProperties); } return node; } }