use of org.teiid.translator.ExecutionFactory.SupportedJoinCriteria in project teiid by teiid.
the class RulePlanJoins method groupAcrossLeftOuter.
private void groupAcrossLeftOuter(QueryMetadataInterface metadata, CapabilitiesFinder capabilitiesFinder, CommandContext context, JoinRegion joinRegion) throws QueryMetadataException, TeiidComponentException, AssertionError {
if (joinRegion.getLeft() == null || joinRegion.getJoinRoot().getLastChild().getType() != NodeConstants.Types.ACCESS || joinRegion.getJoinRoot().getFirstChild().getType() == NodeConstants.Types.ACCESS) {
return;
}
PlanNode planNodeRight = joinRegion.getJoinRoot().getLastChild();
Object modelId = RuleRaiseAccess.getModelIDFromAccess(planNodeRight, metadata);
Map<Object, List<PlanNode>> accessMapLeft = getAccessMap(metadata, capabilitiesFinder, joinRegion.getLeft());
// TODO: what about same connector, but not the same model
List<PlanNode> joinSourcesLeft = accessMapLeft.get(modelId);
if (joinSourcesLeft == null) {
return;
}
SupportedJoinCriteria sjc = CapabilitiesUtil.getSupportedJoinCriteria(modelId, metadata, capabilitiesFinder);
Set<GroupSymbol> groups = new HashSet<GroupSymbol>();
List<Criteria> joinCriteria = (List<Criteria>) joinRegion.getJoinRoot().getProperty(Info.JOIN_CRITERIA);
for (Criteria crit : joinCriteria) {
if (!RuleRaiseAccess.isSupportedJoinCriteria(sjc, crit, modelId, metadata, capabilitiesFinder, null)) {
return;
}
}
GroupsUsedByElementsVisitor.getGroups(joinCriteria, groups);
groups.removeAll(planNodeRight.getGroups());
for (PlanNode planNode : joinSourcesLeft) {
if (!planNode.getGroups().containsAll(groups)) {
continue;
}
// see if we can group the planNode with the other side
if (RuleRaiseAccess.canRaiseOverJoin(Arrays.asList(planNode, planNodeRight), metadata, capabilitiesFinder, joinCriteria, JoinType.JOIN_LEFT_OUTER, null, context, false, false) == null) {
continue;
}
// remove the parent loj, create a new loj
joinRegion.getLeft().getJoinSourceNodes().remove(planNode);
PlanNode joinNode = createJoinNode(planNode, planNodeRight, joinCriteria, JoinType.JOIN_LEFT_OUTER);
PlanNode newAccess = RuleRaiseAccess.raiseAccessOverJoin(joinNode, joinNode.getFirstChild(), modelId, capabilitiesFinder, metadata, false);
for (Set<PlanNode> source : joinRegion.getLeft().getCritieriaToSourceMap().values()) {
if (source.remove(planNode)) {
source.add(newAccess);
}
}
joinRegion.getLeft().getJoinSourceNodes().put(newAccess, newAccess);
PlanNode root = joinRegion.getJoinRoot();
root.getParent().replaceChild(root, root.getFirstChild());
joinRegion.getLeft().reconstructJoinRegoin();
break;
}
}
use of org.teiid.translator.ExecutionFactory.SupportedJoinCriteria in project teiid by teiid.
the class RuleRaiseAccess method canRaiseOverJoin.
static Object canRaiseOverJoin(List<PlanNode> children, QueryMetadataInterface metadata, CapabilitiesFinder capFinder, List<Criteria> crits, JoinType type, AnalysisRecord record, CommandContext context, boolean considerOptional, boolean considerLateral) throws QueryMetadataException, TeiidComponentException {
// we only want to consider binary joins
if (children.size() != 2) {
return null;
}
Object modelID = null;
boolean multiSource = false;
Set<Object> groupIDs = new HashSet<Object>();
int groupCount = 0;
LinkedList<CompareCriteria> thetaCriteria = new LinkedList<CompareCriteria>();
SupportedJoinCriteria sjc = null;
for (PlanNode childNode : children) {
boolean lateral = false;
boolean procedure = false;
if (considerLateral && childNode.getType() == NodeConstants.Types.SOURCE && childNode.getFirstChild() != null && childNode.getProperty(Info.CORRELATED_REFERENCES) != null) {
if (FrameUtil.getNestedPlan(childNode.getFirstChild()) != null) {
return null;
}
Command command = FrameUtil.getNonQueryCommand(childNode.getFirstChild());
if (command instanceof StoredProcedure) {
procedure = true;
if (!CapabilitiesUtil.supports(Capability.QUERY_FROM_PROCEDURE_TABLE, modelID, metadata, capFinder)) {
return null;
}
// this should look like source/project/access - if not, then back out
if (childNode.getFirstChild().getType() == NodeConstants.Types.PROJECT) {
childNode = childNode.getFirstChild();
}
}
if (childNode.getFirstChild().getType() == NodeConstants.Types.ACCESS) {
childNode = childNode.getFirstChild();
} else {
return null;
}
lateral = true;
}
if (childNode.getType() != NodeConstants.Types.ACCESS) {
return null;
}
if (childNode.hasCollectionProperty(NodeConstants.Info.ACCESS_PATTERNS)) {
// $NON-NLS-1$ //$NON-NLS-2$
childNode.recordDebugAnnotation("access pattern not satisfied by join", modelID, "not pushing parent join", record, metadata);
return null;
}
Object accessModelID = getModelIDFromAccess(childNode, metadata);
if (accessModelID == null) {
return null;
}
groupCount += childNode.getGroups().size();
// Add all group metadata IDs to the list but check before each to make
// sure group hasn't already been seen - if so, bail out - this is a self join
// Unless model supports self joins, in which case, don't bail out.
boolean supportsSelfJoins = CapabilitiesUtil.supportsSelfJoins(accessModelID, metadata, capFinder);
if (!supportsSelfJoins) {
for (GroupSymbol groupSymbol : childNode.getGroups()) {
Object groupID = groupSymbol.getMetadataID();
if (!groupIDs.add(groupID)) {
// Already seen group - can't raise access over self join
return null;
}
}
}
// check the join criteria now that we know the model
if (modelID == null) {
if (!CapabilitiesUtil.supportsJoin(accessModelID, type, metadata, capFinder)) {
return null;
}
sjc = CapabilitiesUtil.getSupportedJoinCriteria(accessModelID, metadata, capFinder);
// see if we can emulate the inner join using an outer
if (!type.isOuter() && !CapabilitiesUtil.supports(Capability.QUERY_FROM_JOIN_INNER, accessModelID, metadata, capFinder) && (crits != null) && !crits.isEmpty()) {
// TODO: the IS NOT NULL check is not strictly needed as we could check predicates to see if we are already null filtering
if (!CapabilitiesUtil.supports(Capability.CRITERIA_ISNULL, accessModelID, metadata, capFinder) || !CapabilitiesUtil.supports(Capability.CRITERIA_NOT, accessModelID, metadata, capFinder)) {
return null;
}
if (sjc == SupportedJoinCriteria.ANY) {
// quick check to see if we can find an element to be nullable
boolean valid = false;
for (Criteria crit : crits) {
if (!(crit instanceof CompareCriteria)) {
continue;
}
CompareCriteria cc = (CompareCriteria) crit;
if ((cc.getLeftExpression() instanceof ElementSymbol) || (cc.getRightExpression() instanceof ElementSymbol)) {
valid = true;
}
}
if (!valid) {
// TODO: check if any of the already pushed predicates can satisfy
return null;
}
}
}
if (crits != null && !crits.isEmpty()) {
for (Criteria crit : crits) {
if (!isSupportedJoinCriteria(sjc, crit, accessModelID, metadata, capFinder, record)) {
if (crit instanceof CompareCriteria) {
CompareCriteria cc = (CompareCriteria) crit;
if (cc.isOptional()) {
cc.setOptional(true);
continue;
}
}
// TODO: plan based upon a predicate subset when possible
return null;
} else if (crit instanceof CompareCriteria) {
thetaCriteria.add((CompareCriteria) crit);
}
}
if (sjc == SupportedJoinCriteria.KEY) {
PlanNode left = children.get(0);
PlanNode right = children.get(1);
if (left.getGroups().size() != 1) {
if (right.getGroups().size() != 1) {
// require the simple case of 1 side being a single group
return null;
}
if (type != JoinType.JOIN_INNER) {
return null;
}
left = children.get(1);
right = children.get(0);
}
LinkedList<Expression> leftExpressions = new LinkedList<Expression>();
LinkedList<Expression> rightExpressions = new LinkedList<Expression>();
RuleChooseJoinStrategy.separateCriteria(left.getGroups(), right.getGroups(), leftExpressions, rightExpressions, crits, new LinkedList<Criteria>());
ArrayList<Object> leftIds = new ArrayList<Object>(leftExpressions.size());
ArrayList<Object> rightIds = new ArrayList<Object>(rightExpressions.size());
for (Expression expr : leftExpressions) {
if (expr instanceof ElementSymbol) {
leftIds.add(((ElementSymbol) expr).getMetadataID());
}
}
GroupSymbol rightGroup = null;
for (Expression expr : rightExpressions) {
if (expr instanceof ElementSymbol) {
ElementSymbol es = (ElementSymbol) expr;
if (rightGroup == null) {
rightGroup = es.getGroupSymbol();
} else if (!rightGroup.equals(es.getGroupSymbol())) {
return null;
}
rightIds.add(es.getMetadataID());
}
}
if (rightGroup == null) {
return null;
}
if (!matchesForeignKey(metadata, leftIds, rightIds, left.getGroups().iterator().next(), true, !type.isOuter() || type == JoinType.JOIN_LEFT_OUTER) && !matchesForeignKey(metadata, rightIds, leftIds, rightGroup, true, !type.isOuter())) {
return null;
}
}
}
if (sjc != SupportedJoinCriteria.ANY && thetaCriteria.isEmpty()) {
// cross join not supported
return null;
}
if (type == JoinType.JOIN_LEFT_OUTER && !CapabilitiesUtil.supports(Capability.CRITERIA_ON_SUBQUERY, accessModelID, metadata, capFinder)) {
PlanNode right = children.get(1);
for (PlanNode node : NodeEditor.findAllNodes(right, NodeConstants.Types.SELECT, NodeConstants.Types.SOURCE)) {
for (SubqueryContainer<?> subqueryContainer : ValueIteratorProviderCollectorVisitor.getValueIteratorProviders((Criteria) node.getProperty(NodeConstants.Info.SELECT_CRITERIA))) {
if (!(subqueryContainer instanceof Evaluatable) || subqueryContainer.getCommand().getCorrelatedReferences() != null) {
return null;
}
}
}
}
modelID = accessModelID;
multiSource = childNode.hasBooleanProperty(Info.IS_MULTI_SOURCE);
} else if (!CapabilitiesUtil.isSameConnector(modelID, accessModelID, metadata, capFinder) && !isConformed(metadata, capFinder, (Set<Object>) childNode.getProperty(Info.CONFORMED_SOURCES), modelID, (Set<Object>) children.get(0).getProperty(Info.CONFORMED_SOURCES), accessModelID)) {
return null;
} else if ((multiSource || childNode.hasBooleanProperty(Info.IS_MULTI_SOURCE)) && !context.getOptions().isImplicitMultiSourceJoin()) {
// only allow raise if partitioned
boolean multiSourceOther = childNode.hasBooleanProperty(Info.IS_MULTI_SOURCE);
if (multiSource && multiSourceOther && (type == JoinType.JOIN_ANTI_SEMI || type == JoinType.JOIN_CROSS)) {
return null;
}
ArrayList<Expression> leftExpressions = new ArrayList<Expression>();
ArrayList<Expression> rightExpressions = new ArrayList<Expression>();
RuleChooseJoinStrategy.separateCriteria(children.get(0).getGroups(), children.get(1).getGroups(), leftExpressions, rightExpressions, crits, new LinkedList<Criteria>());
boolean needsOtherCrit = sjc != SupportedJoinCriteria.ANY;
boolean partitioned = !multiSource || !multiSourceOther;
for (int i = 0; i < leftExpressions.size() && (!partitioned || needsOtherCrit); i++) {
boolean multi = isMultiSourceColumn(metadata, leftExpressions.get(i), children.get(0)) && isMultiSourceColumn(metadata, rightExpressions.get(i), children.get(1));
if (multi) {
partitioned = true;
} else {
needsOtherCrit = false;
}
}
if (needsOtherCrit || !partitioned) {
return null;
}
}
if (lateral) {
if ((!CapabilitiesUtil.supports(Capability.QUERY_FROM_JOIN_LATERAL, modelID, metadata, capFinder) || (crits != null && !crits.isEmpty() && !CapabilitiesUtil.supports(Capability.QUERY_FROM_JOIN_LATERAL_CONDITION, accessModelID, metadata, capFinder)))) {
return null;
}
if (!procedure && CapabilitiesUtil.supports(Capability.QUERY_ONLY_FROM_JOIN_LATERAL_PROCEDURE, accessModelID, metadata, capFinder)) {
return null;
}
}
}
// end walking through join node's children
int maxGroups = CapabilitiesUtil.getMaxFromGroups(modelID, metadata, capFinder);
if (maxGroups != -1 && maxGroups < groupCount) {
return null;
}
if (crits != null && !crits.isEmpty()) {
if (considerOptional) {
for (CompareCriteria criteria : thetaCriteria) {
criteria.setOptional(false);
}
} else {
boolean hasCriteria = false;
for (CompareCriteria criteria : thetaCriteria) {
if (criteria.getIsOptional() == null || !criteria.isOptional()) {
hasCriteria = true;
break;
}
}
if (!hasCriteria) {
return null;
}
}
}
return modelID;
}
use of org.teiid.translator.ExecutionFactory.SupportedJoinCriteria 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.translator.ExecutionFactory.SupportedJoinCriteria in project teiid by teiid.
the class CapabilitiesUtil method getSupportedJoinCriteria.
public static SupportedJoinCriteria getSupportedJoinCriteria(Object modelID, QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException {
if (metadata.isVirtualModel(modelID)) {
return SupportedJoinCriteria.ANY;
}
SourceCapabilities caps = getCapabilities(modelID, metadata, capFinder);
SupportedJoinCriteria crits = (SupportedJoinCriteria) caps.getSourceProperty(Capability.JOIN_CRITERIA_ALLOWED);
if (crits == null) {
return SupportedJoinCriteria.ANY;
}
return crits;
}
Aggregations