/*
* 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.Iterator;
import java.util.List;
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.query.analysis.AnalysisRecord;
import org.teiid.query.metadata.QueryMetadataInterface;
import org.teiid.query.optimizer.capabilities.CapabilitiesFinder;
import org.teiid.query.optimizer.relational.OptimizerRule;
import org.teiid.query.optimizer.relational.RelationalPlanner;
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.PlanNode;
import org.teiid.query.rewriter.QueryRewriter;
import org.teiid.query.sql.lang.CompareCriteria;
import org.teiid.query.sql.lang.Criteria;
import org.teiid.query.sql.lang.IsNullCriteria;
import org.teiid.query.sql.lang.JoinType;
import org.teiid.query.sql.symbol.ElementSymbol;
import org.teiid.query.sql.symbol.Expression;
import org.teiid.query.sql.symbol.Function;
import org.teiid.query.sql.symbol.GroupSymbol;
import org.teiid.query.sql.visitor.GroupsUsedByElementsVisitor;
import org.teiid.query.util.CommandContext;
/**
* Pushes on criteria out of the on clause if possible.
*
* If the join no longer contains criteria, it will be changed into a cross join.
*
* Upon a successful push, RulePushSelectCriteria will be run again.
*/
public final class RulePushNonJoinCriteria implements OptimizerRule {
private boolean firstRun = true;
public RulePushNonJoinCriteria(boolean firstRun) {
this.firstRun = firstRun;
}
/**
* Execute the rule as described in the class comments.
* @param plan Incoming query plan, may be modified during method and may be returned from method
* @param metadata Metadata source
* @param rules Rules from optimizer rule stack, may be manipulated during method
* @return Updated query plan if rule fired, else original query plan
*/
public PlanNode execute(PlanNode plan, QueryMetadataInterface metadata, CapabilitiesFinder capFinder, RuleStack rules, AnalysisRecord analysisRecord, CommandContext context)
throws QueryPlannerException, QueryMetadataException, TeiidComponentException {
boolean treeChanged = false;
boolean removeCopiedFlag = false;
boolean pushRuleRaiseNull = false;
for (PlanNode node : NodeEditor.findAllNodes(plan, NodeConstants.Types.JOIN)) {
List criteria = (List)node.getProperty(NodeConstants.Info.JOIN_CRITERIA);
JoinType joinType = (JoinType)node.getProperty(NodeConstants.Info.JOIN_TYPE);
//criteria cannot be pushed out of a full outer join clause
if (joinType == JoinType.JOIN_FULL_OUTER || joinType == JoinType.JOIN_CROSS) {
continue;
}
Iterator crits = criteria.iterator();
while (crits.hasNext()) {
Criteria crit = (Criteria)crits.next();
//special case handling for true/false criteria
if (crit.equals(QueryRewriter.FALSE_CRITERIA) || crit.equals(QueryRewriter.UNKNOWN_CRITERIA)) {
if (joinType == JoinType.JOIN_INNER) {
FrameUtil.replaceWithNullNode(node);
} else {
//must be a left or right outer join, replace the inner side with null
FrameUtil.replaceWithNullNode(JoinUtil.getInnerSideJoinNodes(node)[0]);
removeCopiedFlag = true;
}
//since a null node has been created, raise it to its highest point
pushRuleRaiseNull = true;
treeChanged = true;
break;
} else if (crit.equals(QueryRewriter.TRUE_CRITERIA)) {
crits.remove();
break;
}
if (pushCriteria(node, crit, crits, metadata)) {
treeChanged = true;
}
}
//degrade the join if there is no criteria left
if (criteria.isEmpty() && joinType == JoinType.JOIN_INNER) {
node.setProperty(NodeConstants.Info.JOIN_TYPE, JoinType.JOIN_CROSS);
treeChanged = true;
}
if (removeCopiedFlag) {
//allow the criteria above the join to be eligible for pushing and copying
PlanNode parent = node.getParent();
while (parent != null && parent.getType() == NodeConstants.Types.SELECT) {
parent.setProperty(NodeConstants.Info.IS_COPIED, Boolean.FALSE);
parent = parent.getParent();
}
}
}
if (treeChanged) {
rules.push(RuleConstants.PUSH_SELECT_CRITERIA);
}
if (pushRuleRaiseNull) {
rules.push(RuleConstants.RAISE_NULL);
}
return plan;
}
/**
* True if the criteria is pushed.
*
* It's possible to push to the inner side of the join if the new criteria node
* originates there
*
* @param joinNode
* @param tgtCrit
* @param metadata
* @return
*/
private boolean pushCriteria(PlanNode joinNode,
Criteria tgtCrit, Iterator iter, QueryMetadataInterface metadata) {
PlanNode newCritNode = RelationalPlanner.createSelectNode(tgtCrit, false);
Set<GroupSymbol> groups = newCritNode.getGroups();
PlanNode[] innerJoinNodes = JoinUtil.getInnerSideJoinNodes(joinNode);
boolean pushed = false;
for (int i = 0; i < innerJoinNodes.length; i++) {
if (FrameUtil.findOriginatingNode(innerJoinNodes[i], groups) != null) {
if (pushed) {
//create a new copy since the old one has been used
newCritNode = RelationalPlanner.createSelectNode(tgtCrit, false);
}
innerJoinNodes[i].addAsParent(newCritNode);
pushed = true;
}
}
if (pushed) {
iter.remove();
} else if (firstRun && tgtCrit instanceof CompareCriteria) {
CompareCriteria crit = (CompareCriteria) tgtCrit;
Expression leftExpr = crit.getLeftExpression();
Expression rightExpr = crit.getRightExpression();
for (int i = 0; i < innerJoinNodes.length; i++) {
PlanNode node = FrameUtil.findJoinSourceNode(innerJoinNodes[i]);
boolean outer = false;
for (PlanNode child : NodeEditor.findAllNodes(node, NodeConstants.Types.JOIN)) {
if (((JoinType)child.getProperty(Info.JOIN_TYPE)).isOuter()) {
outer = true;
break;
}
}
if (!outer) {
continue;
}
Set<GroupSymbol> leftExprGroups = GroupsUsedByElementsVisitor.getGroups(leftExpr);
Set<GroupSymbol> rightExprGroups = GroupsUsedByElementsVisitor.getGroups(rightExpr);
ArrayList<ElementSymbol> notNull = new ArrayList<ElementSymbol>(2);
if (node.getGroups().containsAll(leftExprGroups)) {
collectNotNull(leftExpr, notNull);
} else if (node.getGroups().containsAll(rightExprGroups)) {
collectNotNull(rightExpr, notNull);
}
if (!notNull.isEmpty()) {
pushed = true;
for (ElementSymbol es : notNull) {
IsNullCriteria inc = new IsNullCriteria(es);
inc.setNegated(true);
PlanNode notNullCrit = RelationalPlanner.createSelectNode(inc, false);
notNullCrit.setProperty(NodeConstants.Info.IS_TEMPORARY, true);
innerJoinNodes[i].addAsParent(notNullCrit);
}
}
}
}
return pushed;
}
private void collectNotNull(Expression leftExpr,
ArrayList<ElementSymbol> notNull) {
if (leftExpr instanceof ElementSymbol) {
notNull.add((ElementSymbol)leftExpr);
} else if (leftExpr instanceof Function) {
Function f = (Function)leftExpr;
if (!f.getFunctionDescriptor().isNullDependent()) {
for (Expression arg : f.getArgs()) {
collectNotNull(arg, notNull);
}
}
}
}
public String toString() {
return "PushNonJoinCriteria"; //$NON-NLS-1$
}
}