use of org.teiid.query.sql.lang.JoinType in project teiid by teiid.
the class RuleMergeVirtual method checkJoinCriteria.
/**
* check to see if criteria is used in a full outer join or has no groups and is on the inner side of an outer join. if this
* is the case then the layers cannot be merged, since merging would possibly force the criteria to change it's position (into
* the on clause or above the join).
*/
static boolean checkJoinCriteria(PlanNode frameRoot, GroupSymbol virtualGroup, PlanNode parentJoin) {
if (parentJoin != null) {
List<PlanNode> selectNodes = NodeEditor.findAllNodes(frameRoot, NodeConstants.Types.SELECT, NodeConstants.Types.SOURCE);
Set<GroupSymbol> groups = new HashSet<GroupSymbol>();
groups.add(virtualGroup);
for (PlanNode selectNode : selectNodes) {
if (selectNode.hasBooleanProperty(NodeConstants.Info.IS_PHANTOM)) {
continue;
}
JoinType jt = JoinUtil.getJoinTypePreventingCriteriaOptimization(parentJoin, groups);
if (jt != null && (jt == JoinType.JOIN_FULL_OUTER || selectNode.getGroups().size() == 0)) {
return false;
}
}
}
return true;
}
use of org.teiid.query.sql.lang.JoinType in project teiid by teiid.
the class RuleMergeVirtual method checkProjectedSymbols.
static boolean checkProjectedSymbols(GroupSymbol virtualGroup, PlanNode parentJoin, QueryMetadataInterface metadata, List<? extends Expression> selectSymbols, Set<GroupSymbol> groups, boolean checkForNullDependent) {
if (checkForNullDependent) {
checkForNullDependent = false;
// check to see if there are projected literal on the inner side of an outer join that needs to be preserved
if (parentJoin != null) {
PlanNode joinToTest = parentJoin;
while (joinToTest != null) {
JoinType joinType = (JoinType) joinToTest.getProperty(NodeConstants.Info.JOIN_TYPE);
if (joinType == JoinType.JOIN_FULL_OUTER) {
checkForNullDependent = true;
break;
} else if (joinType == JoinType.JOIN_LEFT_OUTER && FrameUtil.findJoinSourceNode(joinToTest.getLastChild()).getGroups().contains(virtualGroup)) {
checkForNullDependent = true;
break;
}
joinToTest = NodeEditor.findParent(joinToTest.getParent(), NodeConstants.Types.JOIN, NodeConstants.Types.SOURCE);
}
}
}
for (int i = 0; i < selectSymbols.size(); i++) {
Expression symbol = selectSymbols.get(i);
Collection scalarSubqueries = ValueIteratorProviderCollectorVisitor.getValueIteratorProviders(symbol);
if (!scalarSubqueries.isEmpty()) {
return false;
}
if (checkForNullDependent && JoinUtil.isNullDependent(metadata, groups, SymbolMap.getExpression(symbol))) {
return false;
}
// TEIID-16: We do not want to merge a non-deterministic scalar function
if (FunctionCollectorVisitor.isNonDeterministic(symbol)) {
return false;
}
}
return true;
}
use of org.teiid.query.sql.lang.JoinType in project teiid by teiid.
the class RulePlanJoins method groupJoinsForPushing.
/**
* This is a heuristic that checks for joins that may be pushed so they can be removed
* before considering the joins that must be evaluated in MetaMatrix.
*
* By running this, we eliminate the need for running RuleRaiseAccess during join ordering
*
* @param metadata
* @param joinRegion
* @throws QueryMetadataException
* @throws TeiidComponentException
* @throws QueryPlannerException
*/
private void groupJoinsForPushing(QueryMetadataInterface metadata, CapabilitiesFinder capFinder, JoinRegion joinRegion, CommandContext context) throws QueryMetadataException, TeiidComponentException, QueryPlannerException {
// TODO: consider moving select criteria if it is preventing a join from being pushed down
// TODO: make the criteria checks based upon a guess at selectivity
Map accessMap = getAccessMap(metadata, capFinder, joinRegion);
boolean structureChanged = false;
// search for combinations of join sources that should be pushed down
for (Iterator accessNodeIter = accessMap.entrySet().iterator(); accessNodeIter.hasNext(); ) {
Map.Entry entry = (Map.Entry) accessNodeIter.next();
List<PlanNode> accessNodes = (List) entry.getValue();
if (accessNodes.size() < 2) {
continue;
}
int secondPass = -1;
for (int i = accessNodes.size() - 1; i >= 0; i--) {
PlanNode accessNode1 = accessNodes.get(i);
Object modelId = RuleRaiseAccess.getModelIDFromAccess(accessNode1, metadata);
SupportedJoinCriteria sjc = CapabilitiesUtil.getSupportedJoinCriteria(modelId, metadata, capFinder);
int discoveredJoin = -1;
for (int k = (secondPass == -1 ? accessNodes.size() - 1 : secondPass); k >= 0; k--) {
if (k == i) {
continue;
}
PlanNode accessNode2 = accessNodes.get(k);
List<PlanNode> criteriaNodes = joinRegion.getCriteriaNodes();
List<PlanNode> joinCriteriaNodes = new LinkedList<PlanNode>();
/* hasJoinCriteria will be true if
* 1. there is criteria between accessNode1 and accessNode2 exclusively
* 2. there is criteria between some other source (not the same logical connector) and accessNode1 or accessNode2
*
* Ideally we should be a little smarter in case 2
* - pushing down a same source cross join can be done if we know that a dependent join will be performed
*/
boolean hasJoinCriteria = false;
LinkedList<Criteria> joinCriteria = new LinkedList<Criteria>();
for (PlanNode critNode : criteriaNodes) {
Set<PlanNode> sources = joinRegion.getCritieriaToSourceMap().get(critNode);
if (sources == null) {
continue;
}
if (sources.contains(accessNode1)) {
if (sources.contains(accessNode2) && sources.size() == 2) {
Criteria crit = (Criteria) critNode.getProperty(NodeConstants.Info.SELECT_CRITERIA);
if (RuleRaiseAccess.isSupportedJoinCriteria(sjc, crit, modelId, metadata, capFinder, null)) {
joinCriteriaNodes.add(critNode);
joinCriteria.add(crit);
}
} else if (!accessNodes.containsAll(sources)) {
hasJoinCriteria = true;
}
} else if (sources.contains(accessNode2) && !accessNodes.containsAll(sources)) {
hasJoinCriteria = true;
}
}
/*
* If we failed to find direct criteria, a cross join may still be acceptable
*/
if (joinCriteriaNodes.isEmpty() && (hasJoinCriteria || !canPushCrossJoin(metadata, accessNode1, accessNode2))) {
continue;
}
List<PlanNode> toTest = Arrays.asList(accessNode1, accessNode2);
JoinType joinType = joinCriteria.isEmpty() ? JoinType.JOIN_CROSS : JoinType.JOIN_INNER;
/*
* We need to limit the heuristic grouping as we don't want to create larger source queries than necessary
*/
boolean shouldPush = true;
int sourceCount = NodeEditor.findAllNodes(accessNode1, NodeConstants.Types.SOURCE, NodeConstants.Types.SOURCE).size();
sourceCount += NodeEditor.findAllNodes(accessNode2, NodeConstants.Types.SOURCE, NodeConstants.Types.SOURCE).size();
if (!context.getOptions().isAggressiveJoinGrouping() && accessMap.size() > 1 && joinType == JoinType.JOIN_INNER && (sourceCount > 2 && (accessNode1.hasProperty(Info.MAKE_DEP) || accessNode2.hasProperty(Info.MAKE_DEP)) || sourceCount > 3) && !canPushCrossJoin(metadata, accessNode1, accessNode2)) {
Collection<GroupSymbol> leftGroups = accessNode1.getGroups();
Collection<GroupSymbol> rightGroups = accessNode2.getGroups();
List<Expression> leftExpressions = new ArrayList<Expression>();
List<Expression> rightExpressions = new ArrayList<Expression>();
List<Criteria> nonEquiJoinCriteria = new ArrayList<Criteria>();
RuleChooseJoinStrategy.separateCriteria(leftGroups, rightGroups, leftExpressions, rightExpressions, joinCriteria, nonEquiJoinCriteria);
// allow a 1-1 join
if (!NewCalculateCostUtil.usesKey(accessNode1, leftExpressions, metadata) || !NewCalculateCostUtil.usesKey(accessNode2, rightExpressions, metadata)) {
// don't push heuristically
shouldPush = false;
}
}
// try to push to the source
if (!shouldPush || RuleRaiseAccess.canRaiseOverJoin(toTest, metadata, capFinder, joinCriteria, joinType, null, context, secondPass != -1, false) == null) {
if (secondPass == -1 && sjc != SupportedJoinCriteria.KEY && discoveredJoin == -1) {
for (Criteria criteria : joinCriteria) {
if (criteria instanceof CompareCriteria && ((CompareCriteria) criteria).isOptional()) {
discoveredJoin = k;
}
}
}
continue;
}
secondPass = -1;
discoveredJoin = -1;
structureChanged = true;
// remove the information that is no longer relevant to the join region
joinRegion.getCritieriaToSourceMap().keySet().removeAll(joinCriteriaNodes);
joinRegion.getCriteriaNodes().removeAll(joinCriteriaNodes);
joinRegion.getJoinSourceNodes().remove(accessNode1);
joinRegion.getJoinSourceNodes().remove(accessNode2);
accessNodes.remove(i);
accessNodes.remove(k < i ? k : k - 1);
// build a new join node
PlanNode joinNode = createJoinNode(accessNode1, accessNode2, joinCriteria, joinType);
PlanNode newAccess = RuleRaiseAccess.raiseAccessOverJoin(joinNode, joinNode.getFirstChild(), entry.getKey(), capFinder, metadata, false);
for (PlanNode critNode : joinCriteriaNodes) {
critNode.removeFromParent();
critNode.removeAllChildren();
}
for (Set<PlanNode> source : joinRegion.getCritieriaToSourceMap().values()) {
if (source.remove(accessNode1) || source.remove(accessNode2)) {
source.add(newAccess);
}
}
joinRegion.getJoinSourceNodes().put(newAccess, newAccess);
accessNodes.add(newAccess);
i = accessNodes.size();
k = accessNodes.size();
break;
}
if (discoveredJoin != -1) {
// rerun with the discoveredJoin criteria
i++;
secondPass = discoveredJoin;
}
}
}
if (structureChanged) {
joinRegion.reconstructJoinRegoin();
}
}
use of org.teiid.query.sql.lang.JoinType in project teiid by teiid.
the class RulePushAggregates method canPush.
/**
* Ensures that we are only pushing through inner equi joins or cross joins. Also collects the necessary staged grouping symbols
* @param aggregates
* @param metadata
* @return null if we cannot push otherwise the target join node
*/
private PlanNode canPush(PlanNode groupNode, Set<Expression> stagedGroupingSymbols, PlanNode planNode, Collection<AggregateSymbol> aggregates, QueryMetadataInterface metadata) {
PlanNode parentJoin = planNode.getParent();
Set<GroupSymbol> groups = FrameUtil.findJoinSourceNode(planNode).getGroups();
PlanNode result = planNode;
while (parentJoin != groupNode) {
if (parentJoin.getType() != NodeConstants.Types.JOIN) {
return null;
}
JoinType joinType = (JoinType) parentJoin.getProperty(NodeConstants.Info.JOIN_TYPE);
if (joinType.isOuter() && aggregates != null) {
for (AggregateSymbol as : aggregates) {
if (as.getArgs().length != 1) {
continue;
}
Collection<GroupSymbol> expressionGroups = GroupsUsedByElementsVisitor.getGroups(as.getArg(0));
Collection<GroupSymbol> innerGroups = null;
if (joinType == JoinType.JOIN_LEFT_OUTER) {
innerGroups = FrameUtil.findJoinSourceNode(parentJoin.getLastChild()).getGroups();
} else {
// full outer
innerGroups = parentJoin.getGroups();
}
if (Collections.disjoint(expressionGroups, innerGroups)) {
continue;
}
if (as.getFunctionDescriptor() != null && as.getFunctionDescriptor().isNullDependent()) {
return null;
}
if (as.getArgs().length == 1 && JoinUtil.isNullDependent(metadata, innerGroups, as.getArg(0))) {
return null;
}
}
}
// check for sideways correlation
PlanNode other = null;
if (planNode == parentJoin.getFirstChild()) {
other = parentJoin.getLastChild();
} else {
other = parentJoin.getFirstChild();
}
SymbolMap map = (SymbolMap) other.getProperty(NodeConstants.Info.CORRELATED_REFERENCES);
if (map != null) {
return null;
// TODO: handle this case. the logic would look something like below,
// but we would need to handle the updating of the symbol maps in addGroupBy
/*filterExpressions(stagedGroupingSymbols, groups, map.getKeys(), true);
for (ElementSymbol ex : map.getKeys()) {
if (DataTypeManager.isNonComparable(DataTypeManager.getDataTypeName(ex.getType()))) {
return null;
}
}*/
}
if (!parentJoin.hasCollectionProperty(NodeConstants.Info.LEFT_EXPRESSIONS) || !parentJoin.hasCollectionProperty(NodeConstants.Info.RIGHT_EXPRESSIONS)) {
List<Criteria> criteria = (List<Criteria>) parentJoin.getProperty(Info.JOIN_CRITERIA);
if (!findStagedGroupingExpressions(groups, criteria, stagedGroupingSymbols)) {
return null;
}
} else {
List<Criteria> criteria = (List<Criteria>) parentJoin.getProperty(Info.NON_EQUI_JOIN_CRITERIA);
if (!findStagedGroupingExpressions(groups, criteria, stagedGroupingSymbols)) {
return null;
}
// we move the target up if the filtered expressions introduce outside groups
if (planNode == parentJoin.getFirstChild()) {
if (filterExpressions(stagedGroupingSymbols, groups, (List<Expression>) parentJoin.getProperty(NodeConstants.Info.LEFT_EXPRESSIONS), true)) {
result = parentJoin;
groups = result.getGroups();
}
} else {
if (filterExpressions(stagedGroupingSymbols, groups, (List<Expression>) parentJoin.getProperty(NodeConstants.Info.RIGHT_EXPRESSIONS), true)) {
result = parentJoin;
groups = result.getGroups();
}
}
}
planNode = parentJoin;
parentJoin = parentJoin.getParent();
}
if (result.getParent() == groupNode) {
// can't be pushed as we are already at the direct child
return null;
}
return result;
}
use of org.teiid.query.sql.lang.JoinType in project teiid by teiid.
the class RuleChooseDependent method markDependent.
/**
* 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)) {
// $NON-NLS-1$ //$NON-NLS-2$
sourceNode.recordDebugAnnotation("node is on outer side of the join", null, "Rejecting dependent join", analysisRecord, null);
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;
}
Aggregations