/*
* 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;
}
}