/*
* 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.Collection;
import java.util.List;
import java.util.Set;
import org.teiid.core.TeiidException;
import org.teiid.core.util.Assertion;
import org.teiid.query.metadata.QueryMetadataInterface;
import org.teiid.query.optimizer.relational.plantree.NodeConstants;
import org.teiid.query.optimizer.relational.plantree.NodeConstants.Info;
import org.teiid.query.optimizer.relational.plantree.PlanNode;
import org.teiid.query.rewriter.QueryRewriter;
import org.teiid.query.sql.LanguageObject;
import org.teiid.query.sql.lang.Criteria;
import org.teiid.query.sql.lang.JoinType;
import org.teiid.query.sql.navigator.PreOrderNavigator;
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.GroupSymbol;
import org.teiid.query.sql.visitor.ExpressionMappingVisitor;
/**
* <p>
* Utility methods for query planning related to joins.
* </p><p>
* In some cases, a query plan can be made more optimal via a few possible
* criteria/join optimizations.
* </p>
*/
public class JoinUtil {
/**
* Can't instantiate
*/
private JoinUtil() {
super();
}
/**
* Will attempt to optimize the join type based upon the criteria provided.
*
* Returns the new join type if one is found, otherwise null
*
* An outer join can be optimized if criteria that is not dependent upon null values
* is applied on the inner side of the join.
*
* @param critNode
* @param joinNode
* @return
*/
static final JoinType optimizeJoinType(PlanNode critNode, PlanNode joinNode, QueryMetadataInterface metadata, boolean modifyJoin) {
if (critNode.getGroups().isEmpty() || !joinNode.getGroups().containsAll(critNode.getGroups()) || joinNode.hasBooleanProperty(Info.PRESERVE)) {
return null;
}
JoinType joinType = (JoinType) joinNode.getProperty(NodeConstants.Info.JOIN_TYPE);
if (!joinType.isOuter()) {
return null;
}
PlanNode left = joinNode.getFirstChild();
left = FrameUtil.findJoinSourceNode(left);
PlanNode right = joinNode.getLastChild();
right = FrameUtil.findJoinSourceNode(right);
Collection<GroupSymbol> outerGroups = left.getGroups();
Collection<GroupSymbol> innerGroups = right.getGroups();
if (joinType == JoinType.JOIN_RIGHT_OUTER) {
outerGroups = innerGroups;
innerGroups = left.getGroups();
}
//sanity check
if ((joinType == JoinType.JOIN_LEFT_OUTER || joinType == JoinType.JOIN_RIGHT_OUTER)
&& outerGroups.containsAll(critNode.getGroups())) {
return null;
}
Criteria crit = (Criteria)critNode.getProperty(NodeConstants.Info.SELECT_CRITERIA);
boolean isNullDepdendent = isNullDependent(metadata, innerGroups, crit);
JoinType result = JoinType.JOIN_INNER;
if (joinType == JoinType.JOIN_LEFT_OUTER || joinType == JoinType.JOIN_RIGHT_OUTER) {
if (isNullDepdendent) {
return null;
}
} else {
boolean isNullDepdendentOther = isNullDependent(metadata, outerGroups, crit);
if (isNullDepdendent && isNullDepdendentOther) {
return null;
}
if (isNullDepdendent && !isNullDepdendentOther) {
result = JoinType.JOIN_LEFT_OUTER;
} else if (!isNullDepdendent && isNullDepdendentOther) {
if (modifyJoin) {
JoinUtil.swapJoinChildren(joinNode);
result = JoinType.JOIN_LEFT_OUTER;
}
}
}
if (modifyJoin) {
joinNode.setProperty(NodeConstants.Info.JOIN_TYPE, result);
}
return result;
}
/**
* Returns true if the given criteria can be anything other than false (or unknown)
* given all null values for elements in the inner groups
*/
public static boolean isNullDependent(QueryMetadataInterface metadata,
final Collection<GroupSymbol> innerGroups,
Criteria crit) {
Criteria simplifiedCrit = (Criteria)replaceWithNullValues(innerGroups, crit);
try {
simplifiedCrit = QueryRewriter.rewriteCriteria(simplifiedCrit, null, metadata);
} catch (TeiidException err) {
//log the exception
return true;
}
return !(simplifiedCrit.equals(QueryRewriter.FALSE_CRITERIA) || simplifiedCrit.equals(QueryRewriter.UNKNOWN_CRITERIA));
}
public static boolean isNullDependent(QueryMetadataInterface metadata,
final Collection<GroupSymbol> innerGroups,
Expression expr) {
Expression simplifiedExpression = (Expression)replaceWithNullValues(innerGroups, expr);
try {
simplifiedExpression = QueryRewriter.rewriteExpression(simplifiedExpression, null, metadata);
} catch (TeiidException err) {
//log the exception
return true;
}
return !QueryRewriter.isNull(simplifiedExpression);
}
private static LanguageObject replaceWithNullValues(final Collection<GroupSymbol> innerGroups,
LanguageObject obj) {
ExpressionMappingVisitor emv = new ExpressionMappingVisitor(null) {
public Expression replaceExpression(Expression element) {
if (!(element instanceof ElementSymbol)) {
return element;
}
ElementSymbol symbol = (ElementSymbol)element;
if (innerGroups.contains(symbol.getGroupSymbol())) {
return new Constant(null, symbol.getType());
}
return element;
}
};
if (obj instanceof ElementSymbol) {
return emv.replaceExpression((ElementSymbol)obj);
}
obj = (LanguageObject)obj.clone();
PreOrderNavigator.doVisit(obj, emv);
return obj;
}
static JoinType getJoinTypePreventingCriteriaOptimization(PlanNode joinNode, PlanNode critNode) {
Set<GroupSymbol> groups = critNode.getGroups();
//special case for 0 group criteria
if (groups.size() == 0) {
critNode = FrameUtil.findOriginatingNode(critNode, groups);
if (critNode == null) {
return null;
}
groups = critNode.getGroups();
}
return getJoinTypePreventingCriteriaOptimization(joinNode, groups);
}
public static JoinType getJoinTypePreventingCriteriaOptimization(PlanNode joinNode,
Set<GroupSymbol> groups) {
JoinType joinType = (JoinType) joinNode.getProperty(NodeConstants.Info.JOIN_TYPE);
if(!joinType.isOuter()) {
return null;
}
if(joinType.equals(JoinType.JOIN_FULL_OUTER)) {
return joinType;
}
Set<GroupSymbol> innerGroups = getInnerSideJoinNodes(joinNode)[0].getGroups();
for (GroupSymbol group : groups) {
if (innerGroups.contains(group)) {
return joinType;
}
}
return null;
}
/**
* Can be called after join planning on a join node to get the inner sides of the join
* @param joinNode
* @return
*/
static PlanNode[] getInnerSideJoinNodes(PlanNode joinNode) {
Assertion.assertTrue(joinNode.getType() == NodeConstants.Types.JOIN);
JoinType jt = (JoinType)joinNode.getProperty(NodeConstants.Info.JOIN_TYPE);
if (jt == JoinType.JOIN_INNER || jt == JoinType.JOIN_CROSS) {
return new PlanNode[] {joinNode.getFirstChild(), joinNode.getLastChild()};
}
if (jt == JoinType.JOIN_RIGHT_OUTER) {
return new PlanNode[] {joinNode.getFirstChild()};
}
if (jt == JoinType.JOIN_LEFT_OUTER) {
return new PlanNode[] {joinNode.getLastChild()};
}
//must be full outer, so there is no inner side
return new PlanNode[] {};
}
/**
* @param joinNode
*/
static void swapJoinChildren(PlanNode joinNode) {
PlanNode leftChild = joinNode.getFirstChild();
joinNode.removeChild(leftChild);
joinNode.addLastChild(leftChild);
List leftExpressions = (List)joinNode.getProperty(NodeConstants.Info.LEFT_EXPRESSIONS);
List rightExpressions = (List)joinNode.getProperty(NodeConstants.Info.RIGHT_EXPRESSIONS);
joinNode.setProperty(NodeConstants.Info.LEFT_EXPRESSIONS, rightExpressions);
joinNode.setProperty(NodeConstants.Info.RIGHT_EXPRESSIONS, leftExpressions);
JoinType jt = (JoinType)joinNode.getProperty(NodeConstants.Info.JOIN_TYPE);
joinNode.setProperty(NodeConstants.Info.JOIN_TYPE, jt.getReverseType());
}
}