/* * 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.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; 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.NodeFactory; import org.teiid.query.optimizer.relational.plantree.PlanNode; import org.teiid.query.processor.relational.JoinNode.JoinStrategyType; 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.lang.Select; 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.util.SymbolMap; import org.teiid.query.util.CommandContext; /** * Perform the optimization:<pre> * source * inner join union all * source inner join * union all => source * a a * b source * source c * union all inner join * c source * d b * source * d * </pre> */ public class RuleDecomposeJoin implements OptimizerRule { @Override public PlanNode execute(PlanNode plan, QueryMetadataInterface metadata, CapabilitiesFinder capabilitiesFinder, RuleStack rules, AnalysisRecord analysisRecord, CommandContext context) throws QueryPlannerException, QueryMetadataException, TeiidComponentException { for (PlanNode joinNode : NodeEditor.findAllNodes(plan, NodeConstants.Types.JOIN, NodeConstants.Types.ACCESS)) { plan = decomposeJoin(joinNode, plan, metadata, context); } return plan; } public PlanNode decomposeJoin(PlanNode joinNode, PlanNode root, QueryMetadataInterface metadata, CommandContext context) throws TeiidComponentException, QueryPlannerException { if (joinNode.getParent() == null) { return root; //already processed } JoinType joinType = (JoinType)joinNode.getProperty(Info.JOIN_TYPE); if (joinType == JoinType.JOIN_ANTI_SEMI || joinType == JoinType.JOIN_CROSS) { return root; } PlanNode left = joinNode.getFirstChild(); while (left.getType() != NodeConstants.Types.SOURCE) { if (left.getType() == NodeConstants.Types.SELECT && left.hasBooleanProperty(Info.IS_PHANTOM)) { left = left.getFirstChild(); } else { return root; } } Map<ElementSymbol, List<Set<Constant>>> partitionInfo = (Map<ElementSymbol, List<Set<Constant>>>)left.getProperty(Info.PARTITION_INFO); if (partitionInfo == null) { return root; } PlanNode unionNode = left.getFirstChild(); if (unionNode.getType() != NodeConstants.Types.SET_OP) { return root; } PlanNode right = joinNode.getLastChild(); while (right.getType() != NodeConstants.Types.SOURCE) { if (right.getType() == NodeConstants.Types.SELECT && right.hasBooleanProperty(Info.IS_PHANTOM)) { right = right.getFirstChild(); } else { return root; } } Map<ElementSymbol, List<Set<Constant>>> rightPartionInfo = (Map<ElementSymbol, List<Set<Constant>>>)right.getProperty(Info.PARTITION_INFO); if (rightPartionInfo == null) { return root; } List<Criteria> criteria = (List<Criteria>)joinNode.getProperty(Info.JOIN_CRITERIA); List<Expression> expr = new ArrayList<Expression>(); List<Expression> exprOther = new ArrayList<Expression>(); RuleChooseJoinStrategy.separateCriteria(unionNode.getParent().getGroups(), right.getGroups(), expr, exprOther, criteria, new LinkedList<Criteria>()); if (expr.isEmpty()) { return root; //no equi-join } List<int[]> matches = findMatches(partitionInfo, rightPartionInfo, expr, exprOther); if (matches == null) { return root; //no non-overlapping partitions } int branchSize = partitionInfo.values().iterator().next().size(); int otherBranchSize = rightPartionInfo.values().iterator().next().size(); if (matches.isEmpty()) { if (joinType == JoinType.JOIN_INNER || joinType == JoinType.JOIN_SEMI) { //no matches mean that we can just insert a null node (false criteria) and be done with it PlanNode critNode = NodeFactory.getNewNode(NodeConstants.Types.SELECT); critNode.setProperty(Info.SELECT_CRITERIA, QueryRewriter.FALSE_CRITERIA); unionNode.addAsParent(critNode); } else if (joinType == JoinType.JOIN_LEFT_OUTER) { joinNode.getParent().replaceChild(joinNode, left); } else if (joinType == JoinType.JOIN_FULL_OUTER) { joinNode.setProperty(Info.JOIN_CRITERIA, QueryRewriter.FALSE_CRITERIA); } return root; } List<PlanNode> branches = new ArrayList<PlanNode>(); //TODO: find union children from RulePushAggregates RulePushSelectCriteria.collectUnionChildren(unionNode, branches); if (branches.size() != branchSize) { return root; //sanity check } List<PlanNode> otherBranches = new ArrayList<PlanNode>(); RulePushSelectCriteria.collectUnionChildren(right.getFirstChild(), otherBranches); if (otherBranches.size() != otherBranchSize) { return root; //sanity check } PlanNode newUnion = buildUnion(unionNode, right, criteria, matches, branches, otherBranches, joinType); PlanNode view = rebuild(left.getGroups().iterator().next(), joinNode, newUnion, metadata, context, left, right); SymbolMap symbolmap = (SymbolMap)view.getProperty(Info.SYMBOL_MAP); HashMap<ElementSymbol, List<Set<Constant>>> newPartitionInfo = new LinkedHashMap<ElementSymbol, List<Set<Constant>>>(); for (int[] match : matches) { updatePartitionInfo(partitionInfo, matches, symbolmap, newPartitionInfo, 0, match[0]); updatePartitionInfo(rightPartionInfo, matches, symbolmap, newPartitionInfo, partitionInfo.size(), match[1]); } view.setProperty(Info.PARTITION_INFO, newPartitionInfo); //since we've created a new union node, there's a chance we can decompose again if (view.getParent().getType() == NodeConstants.Types.JOIN) { return decomposeJoin(view.getParent(), root, metadata, context); } return root; } private void updatePartitionInfo( Map<ElementSymbol, List<Set<Constant>>> partitionInfo, List<int[]> matches, SymbolMap symbolmap, HashMap<ElementSymbol, List<Set<Constant>>> newPartitionInfo, int start, int index) { for (Map.Entry<ElementSymbol, List<Set<Constant>>> entry : partitionInfo.entrySet()) { ElementSymbol newSymbol = symbolmap.getKeys().get(start++); List<Set<Constant>> values = newPartitionInfo.get(newSymbol); if (values == null) { values = new ArrayList<Set<Constant>>(matches.size()); newPartitionInfo.put(newSymbol, values); } values.add(entry.getValue().get(index)); } } /** * Add the new union back in under a view */ static PlanNode rebuild(GroupSymbol group, PlanNode toReplace, PlanNode newUnion, QueryMetadataInterface metadata, CommandContext context, PlanNode... toMap) throws TeiidComponentException, QueryPlannerException, QueryMetadataException { Set<String> groups = context.getGroups(); group = RulePlaceAccess.recontextSymbol(group, groups); PlanNode projectNode = NodeEditor.findNodePreOrder(newUnion, NodeConstants.Types.PROJECT); List<? extends Expression> projectedSymbols = (List<? extends Expression>)projectNode.getProperty(Info.PROJECT_COLS); SymbolMap newSymbolMap = RulePushAggregates.createSymbolMap(group, projectedSymbols, newUnion, metadata); PlanNode view = RuleDecomposeJoin.createSource(group, newUnion, newSymbolMap); Map<Expression, ElementSymbol> inverseMap = newSymbolMap.inserseMapping(); toReplace.getParent().replaceChild(toReplace, view); Set<GroupSymbol> newGroups = Collections.singleton(group); for (PlanNode node : toMap) { FrameUtil.convertFrame(view, node.getGroups().iterator().next(), newGroups, inverseMap, metadata); } return view; } /** * Search each equi-join for partitioning */ private List<int[]> findMatches( Map<ElementSymbol, List<Set<Constant>>> partitionInfo, Map<ElementSymbol, List<Set<Constant>>> partitionInfoOther, List<Expression> expr, List<Expression> exprOther) { List<int[]> matches = null; for (int i = 0; i < expr.size() && matches == null; i++) { if (!(expr.get(i) instanceof ElementSymbol) || !(exprOther.get(i) instanceof ElementSymbol)) { continue; } ElementSymbol es = (ElementSymbol)expr.get(i); ElementSymbol esOther = (ElementSymbol)exprOther.get(i); List<Set<Constant>> partLists = partitionInfo.get(es); List<Set<Constant>> partListsOther = partitionInfoOther.get(esOther); if (partLists == null || partListsOther == null) { continue; } matches = findMatches(partLists, partListsOther); } return matches; } /** * Find overlaps in the given partition lists */ private List<int[]> findMatches(List<Set<Constant>> partLists, List<Set<Constant>> partListsOther) { List<int[]> matches = new LinkedList<int[]>(); for (int j = 0; j < partLists.size(); j++) { int[] match = null; Set<Constant> vals = partLists.get(j); for (int k = 0; k < partListsOther.size(); k++) { if (!Collections.disjoint(vals, partListsOther.get(k))) { if (match == null) { match = new int[] {j, k}; } else { //TODO: we currently do handle a situation where multiple //partitions overlap. return null; } } } if (match != null) { matches.add(match); } } return matches; } private PlanNode buildUnion(PlanNode unionNode, PlanNode otherSide, List<Criteria> criteria, List<int[]> matches, List<PlanNode> branches, List<PlanNode> otherBranches, JoinType joinType) { SymbolMap symbolMap = (SymbolMap)unionNode.getParent().getProperty(Info.SYMBOL_MAP); SymbolMap otherSymbolMap = (SymbolMap)otherSide.getProperty(Info.SYMBOL_MAP); List<PlanNode> joins = new LinkedList<PlanNode>(); for (int i = 0; i < matches.size(); i++) { int[] is = matches.get(i); PlanNode branch = branches.get(is[0]); PlanNode branchSource = createSource(unionNode.getParent().getGroups().iterator().next(), branch, symbolMap); PlanNode otherBranch = otherBranches.get(is[1]); PlanNode otherBranchSource = createSource(otherSide.getGroups().iterator().next(), otherBranch, otherSymbolMap); PlanNode newJoinNode = NodeFactory.getNewNode(NodeConstants.Types.JOIN); newJoinNode.addLastChild(branchSource); newJoinNode.addLastChild(otherBranchSource); newJoinNode.setProperty(Info.JOIN_STRATEGY, JoinStrategyType.NESTED_LOOP); newJoinNode.setProperty(Info.JOIN_TYPE, joinType); newJoinNode.setProperty(Info.JOIN_CRITERIA, LanguageObject.Util.deepClone(criteria, Criteria.class)); newJoinNode.addGroups(branchSource.getGroups()); newJoinNode.addGroups(otherBranchSource.getGroups()); PlanNode projectPlanNode = NodeFactory.getNewNode(NodeConstants.Types.PROJECT); newJoinNode.addAsParent(projectPlanNode); Select allSymbols = new Select(symbolMap.getKeys()); allSymbols.addSymbols(otherSymbolMap.getKeys()); if (i == 0) { QueryRewriter.makeSelectUnique(allSymbols, false); } projectPlanNode.setProperty(NodeConstants.Info.PROJECT_COLS, allSymbols.getSymbols()); projectPlanNode.addGroups(newJoinNode.getGroups()); joins.add(projectPlanNode); } PlanNode newUnion = RulePlanUnions.buildUnionTree(unionNode, joins); return newUnion; } static PlanNode createSource(GroupSymbol group, PlanNode child, SymbolMap symbolMap) { return createSource(group, child, symbolMap.getKeys()); } static PlanNode createSource(GroupSymbol group, PlanNode child, List<ElementSymbol> newProject) { PlanNode branchSource = NodeFactory.getNewNode(NodeConstants.Types.SOURCE); branchSource.addGroup(group); PlanNode projectNode = NodeEditor.findNodePreOrder(child, NodeConstants.Types.PROJECT); branchSource.setProperty(Info.SYMBOL_MAP, SymbolMap.createSymbolMap(newProject, (List<? extends Expression>)projectNode.getProperty(Info.PROJECT_COLS))); child.addAsParent(branchSource); return branchSource; } @Override public String toString() { return "DecomposeJoin"; //$NON-NLS-1$ } }