/*
* 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.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import org.teiid.api.exception.query.QueryMetadataException;
import org.teiid.api.exception.query.QueryPlannerException;
import org.teiid.common.buffer.BufferManager;
import org.teiid.core.TeiidComponentException;
import org.teiid.core.types.DataTypeManager;
import org.teiid.core.types.DataTypeManager.DefaultDataClasses;
import org.teiid.core.util.PropertiesUtils;
import org.teiid.query.analysis.AnalysisRecord;
import org.teiid.query.metadata.QueryMetadataInterface;
import org.teiid.query.optimizer.capabilities.CapabilitiesFinder;
import org.teiid.query.optimizer.capabilities.SourceCapabilities.Capability;
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.NodeFactory;
import org.teiid.query.optimizer.relational.plantree.PlanNode;
import org.teiid.query.optimizer.relational.rules.NewCalculateCostUtil.DependentCostAnalysis;
import org.teiid.query.resolver.util.AccessPattern;
import org.teiid.query.sql.lang.CompareCriteria;
import org.teiid.query.sql.lang.DependentSetCriteria;
import org.teiid.query.sql.lang.DependentSetCriteria.AttributeComparison;
import org.teiid.query.sql.lang.JoinType;
import org.teiid.query.sql.lang.Option.MakeDep;
import org.teiid.query.sql.symbol.Array;
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.util.CommandContext;
/**
* Finds nodes that can be turned into dependent joins
*/
public final class RuleChooseDependent implements OptimizerRule {
private static AtomicInteger ID = new AtomicInteger();
private static class CandidateJoin {
PlanNode joinNode;
boolean leftCandidate;
boolean rightCandidate;
}
public static final int DEFAULT_INDEPENDENT_CARDINALITY = PropertiesUtils.getIntProperty(System.getProperties(), "org.teiid.defaultIndependentCardinality", 10); //$NON-NLS-1$
public static final int UNKNOWN_INDEPENDENT_CARDINALITY = BufferManager.DEFAULT_PROCESSOR_BATCH_SIZE;
private boolean fullPushOnly;
private boolean traditionalOnly;
public RuleChooseDependent() {
}
public RuleChooseDependent(boolean b) {
this.fullPushOnly = b;
}
public PlanNode execute(PlanNode plan, QueryMetadataInterface metadata, CapabilitiesFinder capFinder, RuleStack rules, AnalysisRecord analysisRecord, CommandContext context)
throws QueryPlannerException, QueryMetadataException, TeiidComponentException {
// Find first criteria node in plan with conjuncts
List<CandidateJoin> matches = null;
if (fullPushOnly) {
//full push requires bottom up processing,
matches = findCandidate(plan, metadata, analysisRecord);
Collections.reverse(matches);
} else {
if (!traditionalOnly) {
plan = new RuleChooseDependent(true).execute(plan, metadata, capFinder, rules, analysisRecord, context);
}
matches = findCandidate(plan, metadata, analysisRecord);
}
boolean pushCriteria = false;
// Handle all cases where both siblings are possible matches
for (CandidateJoin entry : matches) {
PlanNode joinNode = entry.joinNode;
if (fullPushOnly && NodeEditor.findParent(joinNode, NodeConstants.Types.ACCESS) != null) {
continue; //already consumed by full pushdown
}
PlanNode sourceNode = entry.leftCandidate?joinNode.getFirstChild():joinNode.getLastChild();
PlanNode siblingNode = entry.leftCandidate?joinNode.getLastChild():joinNode.getFirstChild();
boolean bothCandidates = entry.leftCandidate&&entry.rightCandidate;
PlanNode chosenNode = chooseDepWithoutCosting(sourceNode, bothCandidates?siblingNode:null, analysisRecord);
if(chosenNode != null) {
pushCriteria |= markDependent(chosenNode, joinNode, metadata, null, false, capFinder, context, rules, analysisRecord);
continue;
}
DependentCostAnalysis dca = NewCalculateCostUtil.computeCostForDepJoin(joinNode, !entry.leftCandidate, metadata, capFinder, context);
PlanNode dependentNode = sourceNode;
if (bothCandidates && dca.expectedCardinality == null) {
dca = NewCalculateCostUtil.computeCostForDepJoin(joinNode, true, metadata, capFinder, context);
if (dca.expectedCardinality != null) {
dependentNode = siblingNode;
}
}
if (dca.expectedCardinality != null) {
pushCriteria |= markDependent(dependentNode, joinNode, metadata, dca, null, capFinder, context, rules, analysisRecord);
} else {
float sourceCost = NewCalculateCostUtil.computeCostForTree(sourceNode, metadata);
float siblingCost = NewCalculateCostUtil.computeCostForTree(siblingNode, metadata);
List leftExpressions = (List)joinNode.getProperty(NodeConstants.Info.LEFT_EXPRESSIONS);
List rightExpressions = (List)joinNode.getProperty(NodeConstants.Info.RIGHT_EXPRESSIONS);
float sourceNdv = NewCalculateCostUtil.getNDVEstimate(sourceNode, metadata, sourceCost, entry.leftCandidate?leftExpressions:rightExpressions, true);
float siblingNdv = NewCalculateCostUtil.getNDVEstimate(siblingNode, metadata, siblingCost, entry.leftCandidate?rightExpressions:leftExpressions, true);
if (sourceCost != NewCalculateCostUtil.UNKNOWN_VALUE && sourceNdv == NewCalculateCostUtil.UNKNOWN_VALUE) {
sourceNdv = sourceCost;
}
if (siblingCost != NewCalculateCostUtil.UNKNOWN_VALUE && siblingNdv == NewCalculateCostUtil.UNKNOWN_VALUE) {
siblingNdv = siblingCost;
}
if (bothCandidates && sourceNdv != NewCalculateCostUtil.UNKNOWN_VALUE && ((sourceNdv <= RuleChooseDependent.DEFAULT_INDEPENDENT_CARDINALITY
&& sourceNdv < siblingNdv && sourceCost < 4*siblingCost) || (siblingCost == NewCalculateCostUtil.UNKNOWN_VALUE && sourceNdv <= UNKNOWN_INDEPENDENT_CARDINALITY))) {
pushCriteria |= markDependent(siblingNode, joinNode, metadata, null, sourceCost > RuleChooseDependent.DEFAULT_INDEPENDENT_CARDINALITY?true:null, capFinder, context, rules, analysisRecord);
} else if (siblingNdv != NewCalculateCostUtil.UNKNOWN_VALUE && ((siblingNdv <= RuleChooseDependent.DEFAULT_INDEPENDENT_CARDINALITY && siblingNdv < sourceNdv && siblingCost < 4*sourceCost) || (sourceCost == NewCalculateCostUtil.UNKNOWN_VALUE && siblingNdv <= UNKNOWN_INDEPENDENT_CARDINALITY))) {
pushCriteria |= markDependent(sourceNode, joinNode, metadata, null, siblingCost > RuleChooseDependent.DEFAULT_INDEPENDENT_CARDINALITY?true:null, capFinder, context, rules, analysisRecord);
}
}
}
if (pushCriteria) {
// Insert new rules to push down the SELECT criteria
rules.push(RuleConstants.CLEAN_CRITERIA); //it's important to run clean criteria here since it will remove unnecessary dependent sets
rules.push(RuleConstants.PUSH_SELECT_CRITERIA);
}
if (!matches.isEmpty() && plan.getParent() != null) {
//this can happen if we create a fully pushable plan from full dependent join pushdown
return plan.getParent();
}
return plan;
}
/**
* Walk the tree pre-order, finding all access nodes that are candidates and
* adding them to the matches list.
* @param metadata Metadata implementation
* @param node Root node to search
* @param matches Collection to accumulate matches in
* @throws TeiidComponentException
* @throws QueryMetadataException
*/
List<CandidateJoin> findCandidate(PlanNode root, QueryMetadataInterface metadata, AnalysisRecord analysisRecord) throws QueryMetadataException, TeiidComponentException {
List<CandidateJoin> candidates = new ArrayList<CandidateJoin>();
for (PlanNode joinNode : NodeEditor.findAllNodes(root, NodeConstants.Types.JOIN, NodeConstants.Types.ACCESS)) {
CandidateJoin candidate = null;
for (Iterator<PlanNode> j = joinNode.getChildren().iterator(); j.hasNext();) {
PlanNode child = j.next();
child = FrameUtil.findJoinSourceNode(child);
if(child.hasBooleanProperty(NodeConstants.Info.MAKE_NOT_DEP) || !isValidJoin(joinNode, child, analysisRecord)) {
continue;
}
if (candidate == null) {
candidate = new CandidateJoin();
candidate.joinNode = joinNode;
}
if (j.hasNext()) {
JoinType jtype = (JoinType) joinNode.getProperty(NodeConstants.Info.JOIN_TYPE);
if (!jtype.isOuter()) {
candidate.leftCandidate=true;
candidates.add(candidate);
}
} else {
candidate.rightCandidate=true;
if (!candidate.leftCandidate) {
candidates.add(candidate);
}
}
}
}
return candidates;
}
/**
* Check whether a join is valid. Invalid joins are CROSS JOIN, FULL OUTER JOIN,
* any join without criteria, any join with no equality criteria, and any outer
* join that has the outer side not the same as the dependent.
* @param joinNode The join node to check
* @param sourceNode The access node being considered
* @param analysisRecord
* @return True if valid for making dependent
* @throws TeiidComponentException
* @throws QueryMetadataException
*/
boolean isValidJoin(PlanNode joinNode, PlanNode sourceNode, AnalysisRecord analysisRecord) throws QueryMetadataException, TeiidComponentException {
JoinType jtype = (JoinType) joinNode.getProperty(NodeConstants.Info.JOIN_TYPE);
// Check that join is not a CROSS join or FULL OUTER join
if(jtype.equals(JoinType.JOIN_CROSS)) {
sourceNode.recordDebugAnnotation("parent join is CROSS", null, "Rejecting dependent join", analysisRecord, null); //$NON-NLS-1$ //$NON-NLS-2$
return false;
}
if (!joinNode.getExportedCorrelatedReferences().isEmpty()) {
sourceNode.recordDebugAnnotation("parent join has a correlated nested table", null, "Rejecting dependent join", analysisRecord, null); //$NON-NLS-1$ //$NON-NLS-2$
return false;
}
// Check that join criteria exist
List jcrit = (List) joinNode.getProperty(NodeConstants.Info.JOIN_CRITERIA);
if(jcrit == null || jcrit.size() == 0) {
sourceNode.recordDebugAnnotation("parent join has has no join criteria", null, "Rejecting dependent join", analysisRecord, null); //$NON-NLS-1$ //$NON-NLS-2$
return false;
}
if(joinNode.getProperty(NodeConstants.Info.LEFT_EXPRESSIONS) == null) {
sourceNode.recordDebugAnnotation("parent join has no equa-join predicates", null, "Rejecting dependent join", analysisRecord, null); //$NON-NLS-1$ //$NON-NLS-2$
return false;
}
return true;
}
PlanNode chooseDepWithoutCosting(PlanNode rootNode1, PlanNode rootNode2, AnalysisRecord analysisRecord) throws QueryMetadataException, TeiidComponentException {
PlanNode sourceNode1 = FrameUtil.findJoinSourceNode(rootNode1);
if (sourceNode1.getType() == NodeConstants.Types.GROUP) {
//after push aggregates it's possible that the source is a grouping node
sourceNode1 = FrameUtil.findJoinSourceNode(sourceNode1.getFirstChild());
}
PlanNode sourceNode2 = null;
if (rootNode2 != null) {
sourceNode2 = FrameUtil.findJoinSourceNode(rootNode2);
if (sourceNode2.getType() == NodeConstants.Types.GROUP) {
//after push aggregates it's possible that the source is a grouping node
sourceNode2 = FrameUtil.findJoinSourceNode(sourceNode2.getFirstChild());
}
}
if(sourceNode1.hasCollectionProperty(NodeConstants.Info.ACCESS_PATTERNS) ) {
if (sourceNode2 != null && sourceNode2.hasCollectionProperty(NodeConstants.Info.ACCESS_PATTERNS) ) {
//Return null - query planning should fail because both access nodes
//have unsatisfied access patterns
rootNode1.getParent().recordDebugAnnotation("both children have unsatisfied access patterns", null, "Neither node can be made dependent", analysisRecord, null); //$NON-NLS-1$ //$NON-NLS-2$
return null;
}
rootNode1.recordDebugAnnotation("unsatisfied access pattern detected", null, "marking as dependent side of join", analysisRecord, null); //$NON-NLS-1$ //$NON-NLS-2$
return rootNode1;
} else if (sourceNode2 != null && sourceNode2.hasCollectionProperty(NodeConstants.Info.ACCESS_PATTERNS) ) {
//Access node 2 has unsatisfied access pattern,
//so try to make node 2 dependent
sourceNode2.recordDebugAnnotation("unsatisfied access pattern detected", null, "marking as dependent side of join", analysisRecord, null); //$NON-NLS-1$ //$NON-NLS-2$
return rootNode2;
}
// Check for hints, which over-rule heuristics
if(sourceNode1.hasProperty(NodeConstants.Info.MAKE_DEP)) {
sourceNode1.recordDebugAnnotation("MAKE_DEP hint detected", null, "marking as dependent side of join", analysisRecord, null); //$NON-NLS-1$ //$NON-NLS-2$
rootNode1.setProperty(Info.MAKE_DEP, sourceNode1.getProperty(Info.MAKE_DEP));
return rootNode1;
} else if(sourceNode2 != null && sourceNode2.hasProperty(NodeConstants.Info.MAKE_DEP)) {
sourceNode2.recordDebugAnnotation("MAKE_DEP hint detected", null, "marking as dependent side of join", analysisRecord, null); //$NON-NLS-1$ //$NON-NLS-2$
rootNode2.setProperty(Info.MAKE_DEP, sourceNode2.getProperty(Info.MAKE_DEP));
return rootNode2;
} else if (sourceNode1.hasProperty(NodeConstants.Info.MAKE_IND) && sourceNode2 != null) {
sourceNode2.recordDebugAnnotation("MAKE_IND hint detected", null, "marking as dependent side of join", analysisRecord, null); //$NON-NLS-1$ //$NON-NLS-2$
rootNode2.setProperty(Info.MAKE_DEP, sourceNode1.getProperty(Info.MAKE_IND));
return rootNode2;
} else if (sourceNode2 != null && sourceNode2.hasProperty(NodeConstants.Info.MAKE_IND)) {
sourceNode1.recordDebugAnnotation("MAKE_IND hint detected", null, "marking as dependent side of join", analysisRecord, null); //$NON-NLS-1$ //$NON-NLS-2$
rootNode1.setProperty(Info.MAKE_DEP, sourceNode2.getProperty(Info.MAKE_IND));
return rootNode1;
}
return null;
}
/**
* Mark the specified access node to be made dependent
* @param sourceNode Node to make dependent
* @param dca
* @param rules
* @param analysisRecord
* @param commandContext
* @param capFinder
* @throws TeiidComponentException
* @throws QueryMetadataException
* @throws QueryPlannerException
*/
boolean markDependent(PlanNode sourceNode, PlanNode joinNode, QueryMetadataInterface metadata, DependentCostAnalysis dca,
Boolean bound, CapabilitiesFinder capabilitiesFinder, CommandContext context, RuleStack rules, AnalysisRecord analysisRecord) throws QueryMetadataException, TeiidComponentException, QueryPlannerException {
boolean isLeft = joinNode.getFirstChild() == sourceNode;
// Get new access join node properties based on join criteria
List independentExpressions = (List)(isLeft?joinNode.getProperty(NodeConstants.Info.RIGHT_EXPRESSIONS):joinNode.getProperty(NodeConstants.Info.LEFT_EXPRESSIONS));
List dependentExpressions = (List)(isLeft?joinNode.getProperty(NodeConstants.Info.LEFT_EXPRESSIONS):joinNode.getProperty(NodeConstants.Info.RIGHT_EXPRESSIONS));
if(independentExpressions == null || independentExpressions.isEmpty()) {
return false;
}
PlanNode indNode = isLeft?joinNode.getLastChild():joinNode.getFirstChild();
if (bound == null) {
List<PlanNode> sources = NodeEditor.findAllNodes(indNode, NodeConstants.Types.SOURCE);
for (PlanNode planNode : sources) {
for (GroupSymbol gs : planNode.getGroups()) {
if (gs.isTempTable() && metadata.getCardinality(gs.getMetadataID()) == QueryMetadataInterface.UNKNOWN_CARDINALITY) {
bound = true;
break;
}
}
}
if (bound == null) {
bound = false;
}
}
MakeDep makeDep = (MakeDep)sourceNode.getProperty(Info.MAKE_DEP);
if (fullPushOnly) {
fullyPush(sourceNode, joinNode, metadata, capabilitiesFinder, context, indNode, rules, makeDep, analysisRecord, independentExpressions);
return false;
}
// Check that for a outer join the dependent side must be the inner
JoinType jtype = (JoinType) joinNode.getProperty(NodeConstants.Info.JOIN_TYPE);
if(jtype == JoinType.JOIN_FULL_OUTER || (jtype.isOuter() && JoinUtil.getInnerSideJoinNodes(joinNode)[0] != sourceNode)) {
sourceNode.recordDebugAnnotation("node is on outer side of the join", null, "Rejecting dependent join", analysisRecord, null); //$NON-NLS-1$ //$NON-NLS-2$
return false;
}
String id = nextId();
// Create DependentValueSource and set on the independent side as this will feed the values
joinNode.setProperty(NodeConstants.Info.DEPENDENT_VALUE_SOURCE, id);
PlanNode depNode = isLeft?joinNode.getFirstChild():joinNode.getLastChild();
depNode = FrameUtil.findJoinSourceNode(depNode);
if (!depNode.hasCollectionProperty(Info.ACCESS_PATTERNS)) {
//in some situations a federated join will span multiple tables using the same key
handleDuplicate(joinNode, isLeft, independentExpressions, dependentExpressions);
handleDuplicate(joinNode, !isLeft, dependentExpressions, independentExpressions);
}
PlanNode crit = getDependentCriteriaNode(id, independentExpressions, dependentExpressions, indNode, metadata, dca, bound, makeDep);
sourceNode.addAsParent(crit);
if (isLeft) {
JoinUtil.swapJoinChildren(joinNode);
}
return true;
}
private void handleDuplicate(PlanNode joinNode, boolean isLeft,
List independentExpressions, List dependentExpressions) {
Map<Expression, Integer> seen = new HashMap<Expression, Integer>();
for (int i = 0; i < dependentExpressions.size(); i++) {
Expression ex = (Expression) dependentExpressions.get(i);
Integer index = seen.get(ex);
if (index == null) {
seen.put(ex, i);
} else {
Expression e1 = (Expression)independentExpressions.get(i);
Expression e2 = (Expression)independentExpressions.get(index);
CompareCriteria cc = new CompareCriteria(e1, CompareCriteria.EQ, e2);
PlanNode impliedCriteria = RelationalPlanner.createSelectNode(cc, false);
if (isLeft) {
joinNode.getLastChild().addAsParent(impliedCriteria);
} else {
joinNode.getFirstChild().addAsParent(impliedCriteria);
}
independentExpressions.remove(i);
dependentExpressions.remove(i);
i--;
}
}
}
public static String nextId() {
return "$dsc/id" + ID.getAndIncrement(); //$NON-NLS-1$
}
/**
* Check for fully pushable dependent joins
* currently we only look for the simplistic scenario where there are no intervening
* nodes above the dependent side
* @param independentExpressions
*/
private boolean fullyPush(PlanNode sourceNode, PlanNode joinNode,
QueryMetadataInterface metadata,
CapabilitiesFinder capabilitiesFinder, CommandContext context,
PlanNode indNode,
RuleStack rules, MakeDep makeDep, AnalysisRecord analysisRecord, List independentExpressions) throws QueryMetadataException,
TeiidComponentException, QueryPlannerException {
if (sourceNode.getType() != NodeConstants.Types.ACCESS) {
return false; //don't remove as we may raise an access node to make this possible
}
Object modelID = RuleRaiseAccess.getModelIDFromAccess(sourceNode, metadata);
boolean hasHint = false;
if (makeDep != null && makeDep.getJoin() != null) {
if (!makeDep.getJoin()) {
sourceNode.recordDebugAnnotation("cannot pushdown dependent join", modelID, "honoring hint", analysisRecord, null); //$NON-NLS-1$ //$NON-NLS-2$
return false;
}
hasHint = true;
}
if (!CapabilitiesUtil.supports(Capability.FULL_DEPENDENT_JOIN, modelID, metadata, capabilitiesFinder)) {
if (hasHint) {
sourceNode.recordDebugAnnotation("cannot pushdown dependent join", modelID, "dependent join pushdown needs enabled at the source", analysisRecord, null); //$NON-NLS-1$ //$NON-NLS-2$
}
return false;
}
List<? extends Expression> projected = (List<? extends Expression>) indNode.getProperty(Info.OUTPUT_COLS);
if (projected == null) {
PlanNode plan = sourceNode;
while (plan.getParent() != null) {
plan = plan.getParent();
}
new RuleAssignOutputElements(false).execute(plan, metadata, capabilitiesFinder, null, AnalysisRecord.createNonRecordingRecord(), context);
projected = (List<? extends Expression>) indNode.getProperty(Info.OUTPUT_COLS);
}
if (!hasHint) {
//require no lobs
for (Expression ex : projected) {
if (DataTypeManager.isLOB(ex.getClass())) {
return false;
}
}
//old optimizer tests had no buffermanager
if (context.getBufferManager() == null) {
return false;
}
if (makeDep != null && makeDep.getMax() != null) {
//if the user specifies a max, it's best to just use a regular dependent join
return false;
}
}
/*
* check to see how far the access node can be raised
*/
PlanNode tempAccess = NodeFactory.getNewNode(NodeConstants.Types.ACCESS);
GroupSymbol gs = RulePlaceAccess.recontextSymbol(new GroupSymbol("TEIID_TEMP"), context.getGroups()); //$NON-NLS-1$
gs.setDefinition(null);
tempAccess.addGroup(gs);
tempAccess.setProperty(Info.MODEL_ID, modelID);
indNode.addAsParent(tempAccess);
PlanNode originalSource = sourceNode;
sourceNode = originalSource.clone();
//more deeply clone
if (sourceNode.hasCollectionProperty(Info.ACCESS_PATTERNS)) {
sourceNode.setProperty(Info.ACCESS_PATTERNS, new ArrayList<AccessPattern>((List)sourceNode.getProperty(Info.ACCESS_PATTERNS)));
}
if (sourceNode.hasCollectionProperty(Info.CONFORMED_SOURCES)) {
sourceNode.setProperty(Info.CONFORMED_SOURCES, new LinkedHashSet<Object>((Set)sourceNode.getProperty(Info.CONFORMED_SOURCES)));
}
originalSource.addAsParent(sourceNode);
boolean raised = false;
boolean moreProcessing = false;
boolean first = true;
while (sourceNode.getParent() != null && RuleRaiseAccess.raiseAccessNode(sourceNode, sourceNode, metadata, capabilitiesFinder, true, null, context) != null) {
raised = true;
if (first) { //raising over join required
first = false;
continue;
}
switch (sourceNode.getFirstChild().getType()) {
case NodeConstants.Types.PROJECT:
//TODO: check for correlated subqueries
if (sourceNode.getFirstChild().hasBooleanProperty(Info.HAS_WINDOW_FUNCTIONS)) {
moreProcessing = true;
}
break;
case NodeConstants.Types.SORT:
case NodeConstants.Types.DUP_REMOVE:
case NodeConstants.Types.GROUP:
case NodeConstants.Types.SELECT:
case NodeConstants.Types.TUPLE_LIMIT:
case NodeConstants.Types.JOIN:
moreProcessing = true;
break;
}
}
if (!raised) {
tempAccess.getParent().replaceChild(tempAccess, tempAccess.getFirstChild());
sourceNode.getParent().replaceChild(sourceNode, sourceNode.getFirstChild());
return false;
}
if (!moreProcessing && !hasHint) {
//restore the plan
if (sourceNode.getParent() != null) {
sourceNode.getParent().replaceChild(sourceNode, sourceNode.getFirstChild());
} else {
sourceNode.removeAllChildren();
}
return false;
}
originalSource.getParent().replaceChild(originalSource, originalSource.getFirstChild());
//all the references to any groups from this join have to changed over to the new group
//and we need to insert a source/project node to turn this into a proper plan
PlanNode project = NodeFactory.getNewNode(NodeConstants.Types.PROJECT);
PlanNode source = NodeFactory.getNewNode(NodeConstants.Types.SOURCE);
source.addGroup(gs);
project.setProperty(Info.OUTPUT_COLS, projected);
project.setProperty(Info.PROJECT_COLS, projected);
Set<GroupSymbol> newGroups = Collections.singleton(gs);
ArrayList<ElementSymbol> virtualSymbols = new ArrayList<ElementSymbol>(projected.size());
for (int i = 0; i < projected.size(); i++) {
ElementSymbol es = new ElementSymbol("col" + (i+1)); //$NON-NLS-1$
Expression ex = projected.get(i);
es.setType(ex.getType());
virtualSymbols.add(es);
//TODO: set a metadata id from either side
if (ex instanceof ElementSymbol) {
es.setMetadataID(((ElementSymbol)ex).getMetadataID());
}
}
List<ElementSymbol> newCols = RulePushAggregates.defineNewGroup(gs, virtualSymbols, metadata);
SymbolMap symbolMap = SymbolMap.createSymbolMap(newCols, projected);
Map<Expression, ElementSymbol> inverse = symbolMap.inserseMapping();
//TODO: the util logic should handle multiple groups
for (GroupSymbol group : indNode.getGroups()) {
FrameUtil.convertFrame(joinNode, group, newGroups, inverse, metadata);
}
//add the source a new group for the join
indNode.addAsParent(source);
//convert the lower plan into a subplan
//it needs to be rooted by a project - a view isn't really needed
indNode.removeFromParent();
project.addFirstChild(indNode);
//run the remaining rules against the subplan
RuleStack ruleCopy = rules.clone();
RuleChooseDependent ruleChooseDependent = new RuleChooseDependent();
ruleChooseDependent.traditionalOnly = true;
ruleCopy.push(ruleChooseDependent);
if (indNode.getType() == NodeConstants.Types.ACCESS) {
PlanNode root = RuleRaiseAccess.raiseAccessNode(project, indNode, metadata, capabilitiesFinder, true, null, context);
if (root != project) {
project = root;
}
}
//fully plan the sub-plan with the remaining rules
project = rules.getPlanner().executeRules(ruleCopy, project);
source.setProperty(Info.SYMBOL_MAP, symbolMap);
source.setProperty(Info.SUB_PLAN, project);
return true;
}
/**
* @param independentExpressions
* @param dependentExpressions
* @param makeDep
* @return
* @throws TeiidComponentException
* @throws QueryMetadataException
* @since 4.3
*/
public static PlanNode getDependentCriteriaNode(String id, List<Expression> independentExpressions,
List<Expression> dependentExpressions, PlanNode indNode, QueryMetadataInterface metadata, DependentCostAnalysis dca, Boolean bound, MakeDep makeDep) throws QueryMetadataException, TeiidComponentException {
Float cardinality = null;
List<DependentSetCriteria.AttributeComparison> expressions = new ArrayList<DependentSetCriteria.AttributeComparison>(dependentExpressions.size());
for (int i = 0; i < dependentExpressions.size(); i++) {
Expression depExpr = dependentExpressions.get(i);
Expression indExpr = independentExpressions.get(i);
DependentSetCriteria.AttributeComparison comp = new DependentSetCriteria.AttributeComparison();
if (dca != null && dca.expectedNdv[i] != null) {
if (dca.expectedNdv[i] > 4*dca.maxNdv[i]) {
continue; //not necessary to use
}
comp.ndv = dca.expectedNdv[i];
comp.maxNdv = dca.maxNdv[i];
} else {
Collection<ElementSymbol> elems = ElementCollectorVisitor.getElements(indExpr, true);
if (cardinality == null) {
cardinality = NewCalculateCostUtil.computeCostForTree(indNode, metadata);
}
comp.ndv = NewCalculateCostUtil.getNDVEstimate(indNode, metadata, cardinality, elems, true);
if (bound) {
if (dca != null) {
comp.maxNdv = Math.max(comp.ndv * 4, dca.expectedCardinality * 2);
} else {
comp.maxNdv = Math.max(UNKNOWN_INDEPENDENT_CARDINALITY, comp.ndv * 4);
}
}
}
comp.ind = indExpr;
comp.dep = SymbolMap.getExpression(depExpr);
expressions.add(comp);
}
PlanNode result = createDependentSetNode(id, expressions);
if (makeDep != null) {
DependentSetCriteria dsc = (DependentSetCriteria)result.getProperty(Info.SELECT_CRITERIA);
dsc.setMakeDepOptions(makeDep);
}
return result;
}
static PlanNode createDependentSetNode(String id, List<DependentSetCriteria.AttributeComparison> expressions) {
DependentSetCriteria crit = createDependentSetCriteria(id, expressions);
PlanNode selectNode = RelationalPlanner.createSelectNode(crit, false);
selectNode.setProperty(NodeConstants.Info.IS_DEPENDENT_SET, Boolean.TRUE);
return selectNode;
}
static DependentSetCriteria createDependentSetCriteria(String id, List<DependentSetCriteria.AttributeComparison> expressions) {
if (expressions.isEmpty()) {
return null;
}
Expression indEx = null;
Expression depEx = null;
float maxNdv = NewCalculateCostUtil.UNKNOWN_VALUE;
float ndv = NewCalculateCostUtil.UNKNOWN_VALUE;
if (expressions.size() == 1) {
AttributeComparison attributeComparison = expressions.get(0);
indEx = attributeComparison.ind;
depEx = attributeComparison.dep;
maxNdv = attributeComparison.maxNdv;
ndv = attributeComparison.ndv;
} else {
List<Expression> indExprs = new ArrayList<Expression>(expressions.size());
List<Expression> depExprs = new ArrayList<Expression>(expressions.size());
boolean unknown = false;
for (DependentSetCriteria.AttributeComparison comp : expressions) {
indExprs.add(comp.ind);
depExprs.add(comp.dep);
if (comp.ndv == NewCalculateCostUtil.UNKNOWN_VALUE) {
ndv = NewCalculateCostUtil.UNKNOWN_VALUE;
maxNdv = NewCalculateCostUtil.UNKNOWN_VALUE;
unknown = true;
} else if (!unknown) {
ndv = Math.max(ndv, comp.ndv);
maxNdv = Math.max(maxNdv, comp.maxNdv);
}
}
//TODO: detect a base type
indEx = new Array(DefaultDataClasses.OBJECT, indExprs);
depEx = new Array(DefaultDataClasses.OBJECT, depExprs);
}
DependentSetCriteria crit = new DependentSetCriteria(depEx, id);
crit.setValueExpression(indEx);
crit.setAttributes(expressions);
crit.setMaxNdv(maxNdv);
crit.setNdv(ndv);
return crit;
}
public String toString() {
return "ChooseDependent"; //$NON-NLS-1$
}
}