/* * 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.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashSet; 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.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.sql.lang.Criteria; import org.teiid.query.sql.lang.JoinType; import org.teiid.query.sql.symbol.GroupSymbol; import org.teiid.query.sql.visitor.GroupsUsedByElementsVisitor; import org.teiid.query.util.CommandContext; /* * TODO: may need to rerun plan joins sequence after this */ public class RulePlanOuterJoins implements OptimizerRule { @Override public PlanNode execute(PlanNode plan, QueryMetadataInterface metadata, CapabilitiesFinder capabilitiesFinder, RuleStack rules, AnalysisRecord analysisRecord, CommandContext context) throws QueryPlannerException, QueryMetadataException, TeiidComponentException { while (planLeftOuterJoinAssociativity(plan, metadata, capabilitiesFinder, analysisRecord, context)) { //repeat } return plan; } private boolean planLeftOuterJoinAssociativity(PlanNode plan, QueryMetadataInterface metadata, CapabilitiesFinder capabilitiesFinder, AnalysisRecord analysisRecord, CommandContext context) throws QueryMetadataException, TeiidComponentException { boolean changedAny = false; LinkedHashSet<PlanNode> joins = new LinkedHashSet<PlanNode>(NodeEditor.findAllNodes(plan, NodeConstants.Types.JOIN, NodeConstants.Types.ACCESS)); while (!joins.isEmpty()) { Iterator<PlanNode> i = joins.iterator(); PlanNode join = i.next(); i.remove(); if (!join.getProperty(Info.JOIN_TYPE).equals(JoinType.JOIN_LEFT_OUTER)) { continue; } PlanNode childJoin = null; PlanNode other = null; PlanNode left = join.getFirstChild(); PlanNode right = join.getLastChild(); if (left.getType() == NodeConstants.Types.JOIN && left.getProperty(Info.JOIN_TYPE) == JoinType.JOIN_LEFT_OUTER) { childJoin = left; other = right; } else if (right.getType() == NodeConstants.Types.JOIN && (right.getProperty(Info.JOIN_TYPE) == JoinType.JOIN_LEFT_OUTER || right.getProperty(Info.JOIN_TYPE) == JoinType.JOIN_INNER)) { childJoin = right; other = left; } else { continue; } PlanNode cSource = other; if (cSource.getType() != NodeConstants.Types.ACCESS) { continue; } List<Criteria> joinCriteria = (List<Criteria>) join.getProperty(Info.JOIN_CRITERIA); if (!isCriteriaValid(joinCriteria, metadata, join)) { continue; } List<Criteria> childJoinCriteria = (List<Criteria>) childJoin.getProperty(Info.JOIN_CRITERIA); if (!isCriteriaValid(childJoinCriteria, metadata, childJoin)) { continue; } //there are three forms we can take // (a b) c -> a (b c) or (a c) b // c (b a) -> (c b) a or (c a) b Set<GroupSymbol> groups = GroupsUsedByElementsVisitor.getGroups(joinCriteria); if (Collections.disjoint(groups, FrameUtil.findJoinSourceNode(childJoin == left?childJoin.getFirstChild():childJoin.getLastChild()).getGroups())) { //case where absolute order remains the same PlanNode bSource = childJoin == left?childJoin.getLastChild():childJoin.getFirstChild(); if (bSource.getType() != NodeConstants.Types.ACCESS) { continue; } Object modelId = RuleRaiseAccess.canRaiseOverJoin(childJoin == left?Arrays.asList(bSource, cSource):Arrays.asList(cSource, bSource), metadata, capabilitiesFinder, joinCriteria, JoinType.JOIN_LEFT_OUTER, analysisRecord, context, false, false); if (modelId == null) { continue; } //rearrange PlanNode newParent = RulePlanJoins.createJoinNode(); newParent.setProperty(Info.JOIN_TYPE, JoinType.JOIN_LEFT_OUTER); PlanNode newChild = RulePlanJoins.createJoinNode(); newChild.setProperty(Info.JOIN_TYPE, JoinType.JOIN_LEFT_OUTER); joins.remove(childJoin); if (childJoin == left) { //a (b c) newChild.addFirstChild(childJoin.getLastChild()); newChild.addLastChild(other); newChild.setProperty(Info.JOIN_CRITERIA, joinCriteria); newParent.addFirstChild(childJoin.getFirstChild()); newParent.addLastChild(newChild); newParent.setProperty(Info.JOIN_CRITERIA, childJoinCriteria); } else { //(c b) a newChild.addFirstChild(other); newChild.addLastChild(childJoin.getFirstChild()); newChild.setProperty(Info.JOIN_CRITERIA, joinCriteria); newParent.addFirstChild(newChild); newParent.addLastChild(childJoin.getLastChild()); newParent.setProperty(Info.JOIN_CRITERIA, childJoinCriteria); } updateGroups(newChild); updateGroups(newParent); join.getParent().replaceChild(join, newParent); if(RuleRaiseAccess.checkConformedSubqueries(newChild.getFirstChild(), newChild, true)) { RuleRaiseAccess.raiseAccessOverJoin(newChild, newChild.getFirstChild(), modelId, capabilitiesFinder, metadata, true); changedAny = true; } } else if (Collections.disjoint(groups, FrameUtil.findJoinSourceNode(childJoin == right?childJoin.getFirstChild():childJoin.getLastChild()).getGroups())) { PlanNode aSource = childJoin == left?childJoin.getFirstChild():childJoin.getLastChild(); if (aSource.getType() != NodeConstants.Types.ACCESS) { continue; } if (!join.getExportedCorrelatedReferences().isEmpty()) { //TODO: we are not really checking that specifically continue; } Object modelId = RuleRaiseAccess.canRaiseOverJoin(childJoin == left?Arrays.asList(aSource, cSource):Arrays.asList(cSource, aSource), metadata, capabilitiesFinder, joinCriteria, JoinType.JOIN_LEFT_OUTER, analysisRecord, context, false, false); if (modelId == null) { continue; } //rearrange PlanNode newParent = RulePlanJoins.createJoinNode(); newParent.setProperty(Info.JOIN_TYPE, JoinType.JOIN_LEFT_OUTER); PlanNode newChild = RulePlanJoins.createJoinNode(); newChild.setProperty(Info.JOIN_TYPE, JoinType.JOIN_LEFT_OUTER); joins.remove(childJoin); if (childJoin == left) { newChild.addFirstChild(childJoin.getFirstChild()); newChild.addLastChild(other); newParent.addLastChild(childJoin.getLastChild()); } else { newChild.addFirstChild(other); newChild.addLastChild(childJoin.getLastChild()); newParent.addLastChild(childJoin.getFirstChild()); } newChild.addGroups(newChild.getFirstChild().getGroups()); newChild.setProperty(Info.JOIN_CRITERIA, joinCriteria); newParent.addFirstChild(newChild); newParent.setProperty(Info.JOIN_CRITERIA, childJoinCriteria); updateGroups(newChild); updateGroups(newParent); join.getParent().replaceChild(join, newParent); if(RuleRaiseAccess.checkConformedSubqueries(newChild.getFirstChild(), newChild, true)) { RuleRaiseAccess.raiseAccessOverJoin(newChild, newChild.getFirstChild(), modelId, capabilitiesFinder, metadata, true); changedAny = true; } } } return changedAny; } private void updateGroups(PlanNode node) { node.addGroups(GroupsUsedByElementsVisitor.getGroups(node.getCorrelatedReferenceElements())); node.addGroups(FrameUtil.findJoinSourceNode(node.getFirstChild()).getGroups()); node.addGroups(FrameUtil.findJoinSourceNode(node.getLastChild()).getGroups()); } private boolean isCriteriaValid(List<Criteria> joinCriteria, QueryMetadataInterface metadata, PlanNode join) { if (joinCriteria.isEmpty()) { return false; } Set<GroupSymbol> groups = join.getGroups(); for (Criteria crit : joinCriteria) { if (JoinUtil.isNullDependent(metadata, groups, crit)) { return false; } } return true; } @Override public String toString() { return "PlanOuterJoins"; //$NON-NLS-1$ } }