/*
* 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.*;
import org.teiid.api.exception.query.QueryMetadataException;
import org.teiid.api.exception.query.QueryPlannerException;
import org.teiid.core.TeiidComponentException;
import org.teiid.query.QueryPlugin;
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.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.resolver.util.AccessPattern;
import org.teiid.query.sql.LanguageObject;
import org.teiid.query.sql.lang.CompareCriteria;
import org.teiid.query.sql.lang.CompoundCriteria;
import org.teiid.query.sql.lang.Criteria;
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.SubqueryContainer;
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.symbol.WindowFunction;
import org.teiid.query.sql.symbol.WindowSpecification;
import org.teiid.query.sql.util.SymbolMap;
import org.teiid.query.sql.visitor.ElementCollectorVisitor;
import org.teiid.query.sql.visitor.EvaluatableVisitor;
import org.teiid.query.sql.visitor.GroupsUsedByElementsVisitor;
import org.teiid.query.sql.visitor.ValueIteratorProviderCollectorVisitor;
import org.teiid.query.util.CommandContext;
public final class RulePushSelectCriteria implements OptimizerRule {
private List<PlanNode> createdNodes;
public List<PlanNode> getCreatedNodes() {
return createdNodes;
}
public void setCreatedNodes(List<PlanNode> createdNodes) {
this.createdNodes = createdNodes;
}
/**
* Execute the rule as described in the class comments.
* @param plan Incoming query plan, may be modified during method and may be returned from method
* @param metadata Metadata source
* @param rules Rules from optimizer rule stack, may be manipulated during method
* @return Updated query plan if rule fired, else original query plan
*/
public PlanNode execute(PlanNode plan, QueryMetadataInterface metadata, CapabilitiesFinder capFinder, RuleStack rules, AnalysisRecord analysisRecord, CommandContext context)
throws QueryPlannerException, QueryMetadataException, TeiidComponentException {
// Initialize movedNode to true so the loop will start
boolean movedAnyNode = true;
// Create set of nodes that no longer need to be considered
Set<PlanNode> deadNodes = new HashSet<PlanNode>();
// Loop while criteria nodes are still being moved
while(movedAnyNode) {
// Reset flag to false for this iteration
movedAnyNode = false;
// Find criteria nodes that could be pushed
List<PlanNode> critNodes = NodeEditor.findAllNodes(plan, NodeConstants.Types.SELECT);
Collections.reverse(critNodes);
for (PlanNode critNode : critNodes) {
boolean isPhantom = critNode.hasBooleanProperty(NodeConstants.Info.IS_PHANTOM);
boolean isCopied = critNode.hasBooleanProperty(NodeConstants.Info.IS_COPIED);
boolean isPushed = critNode.hasBooleanProperty(NodeConstants.Info.IS_PUSHED);
if (isPhantom || isCopied || isPushed || deadNodes.contains(critNode)) {
continue;
}
PlanNode sourceNode = findOriginatingNode(metadata, capFinder, critNode, analysisRecord);
if(sourceNode == null) {
deadNodes.add(critNode);
continue;
}
boolean moved = pushTowardOriginatingNode(sourceNode, critNode, metadata, capFinder);
if(critNode.hasBooleanProperty(Info.IS_PUSHED) || (critNode.getGroups().isEmpty() && critNode.getSubqueryContainers().isEmpty()) || !atBoundary(critNode, sourceNode)) {
deadNodes.add(critNode);
movedAnyNode |= moved;
continue;
}
switch (sourceNode.getType()) {
case NodeConstants.Types.SOURCE:
{
Boolean acrossFrame = pushAcrossFrame(sourceNode, critNode, metadata);
if (acrossFrame != null) {
moved = acrossFrame;
} else {
movedAnyNode = true; //new nodes created
}
break;
}
case NodeConstants.Types.JOIN:
{
//pushing below a join is not necessary under an access node
if (NodeEditor.findParent(critNode, NodeConstants.Types.ACCESS) == null && critNode.getSubqueryContainers().isEmpty()) {
moved = handleJoinCriteria(sourceNode, critNode, metadata);
}
break;
}
case NodeConstants.Types.GROUP:
{
moved = pushAcrossGroupBy(sourceNode, critNode, metadata, true, capFinder);
}
}
if (!moved) {
deadNodes.add(critNode);
} else {
movedAnyNode = true;
}
}
}
return plan;
}
boolean pushAcrossGroupBy(PlanNode sourceNode,
PlanNode critNode, QueryMetadataInterface metadata, boolean inPlan, CapabilitiesFinder capFinder)
throws QueryPlannerException, QueryMetadataException, TeiidComponentException {
if (critNode.hasBooleanProperty(NodeConstants.Info.IS_HAVING)) {
return false;
}
if (critNode.hasBooleanProperty(NodeConstants.Info.IS_DEPENDENT_SET)) {
PlanNode accessNode = NodeEditor.findParent(critNode, NodeConstants.Types.ACCESS);
if (accessNode != null) {
List<Expression> cols = (List<Expression>) sourceNode.getProperty(Info.GROUP_COLS);
if (cols != null) {
boolean hasExpression = false;
boolean hasLiteral = false;
for (final Iterator<Expression> iterator = cols.iterator(); iterator.hasNext();) {
Expression ex = iterator.next();
hasExpression |= !(ex instanceof ElementSymbol);
hasLiteral |= EvaluatableVisitor.willBecomeConstant(ex, true);
}
if (hasLiteral || (hasExpression && !CapabilitiesUtil.supports(Capability.QUERY_FUNCTIONS_IN_GROUP_BY, RuleRaiseAccess.getModelIDFromAccess(accessNode, metadata), metadata, capFinder))) {
markDependent(critNode, accessNode, metadata, capFinder);
critNode.setProperty(Info.IS_HAVING, true);
return false;
}
}
}
}
if (sourceNode.hasBooleanProperty(Info.ROLLUP)) {
//there is a pre/post affect. we can push, but then there is
//null filtering that may need to occur
//the more complicated approach would be to determine the effective null tests,
//but instead we'll just clone the node
if (inPlan) {
PlanNode copy = copyNode(critNode);
critNode.setProperty(Info.IS_PUSHED, true);
critNode.setProperty(Info.IS_HAVING, true);
critNode.getFirstChild().addAsParent(copy);
critNode = copy;
}
}
boolean moved = false;
SymbolMap symbolMap = (SymbolMap) sourceNode.getProperty(NodeConstants.Info.SYMBOL_MAP);
FrameUtil.convertNode(critNode, null, null, symbolMap.asMap(), metadata, true);
if (inPlan) {
NodeEditor.removeChildNode(critNode.getParent(), critNode);
sourceNode.getFirstChild().addAsParent(critNode);
}
moved = true;
if (critNode.hasBooleanProperty(NodeConstants.Info.IS_DEPENDENT_SET)) {
PlanNode accessNode = NodeEditor.findParent(critNode, NodeConstants.Types.ACCESS);
if (accessNode != null) {
markDependent(critNode, accessNode, metadata, capFinder);
moved = false; //terminal position
}
}
return moved;
}
private PlanNode findOriginatingNode(QueryMetadataInterface metadata,
CapabilitiesFinder capFinder, PlanNode critNode, AnalysisRecord record)
throws TeiidComponentException, QueryMetadataException {
if (critNode.getGroups().isEmpty()) {
//check to see if pushing may impact cardinality
PlanNode groupNode = NodeEditor.findNodePreOrder(critNode, NodeConstants.Types.GROUP, NodeConstants.Types.SOURCE);
if (groupNode != null && !groupNode.hasCollectionProperty(NodeConstants.Info.GROUP_COLS)) {
return groupNode;
}
Object modelId = getSubqueryModelId(metadata, capFinder, critNode, record);
if (modelId != null) {
for (PlanNode node : NodeEditor.findAllNodes(critNode, NodeConstants.Types.SOURCE)) {
GroupSymbol group = node.getGroups().iterator().next();
Object srcModelID = metadata.getModelID(group.getMetadataID());
if(CapabilitiesUtil.isSameConnector(srcModelID, modelId, metadata, capFinder)) {
return node;
}
}
}
}
return FrameUtil.findOriginatingNode(critNode, critNode.getGroups());
}
private Object getSubqueryModelId(QueryMetadataInterface metadata,
CapabilitiesFinder capFinder, PlanNode critNode, AnalysisRecord record)
throws TeiidComponentException, QueryMetadataException {
Object modelId = null;
for (SubqueryContainer subqueryContainer : critNode.getSubqueryContainers()) {
Object validId = CriteriaCapabilityValidatorVisitor.validateSubqueryPushdown(subqueryContainer, null, metadata, capFinder, record);
if (validId == null) {
return null;
}
if (modelId == null) {
modelId = validId;
} else if (!CapabilitiesUtil.isSameConnector(modelId, validId, metadata, capFinder)) {
return null;
}
}
return modelId;
}
/**
* Handles multi-group criteria originating at the given joinNode
*
* @param joinNode
* @param critNode
* @return
*/
private boolean handleJoinCriteria(PlanNode joinNode, PlanNode critNode, QueryMetadataInterface metadata) {
JoinType jt = (JoinType)joinNode.getProperty(NodeConstants.Info.JOIN_TYPE);
if (jt == JoinType.JOIN_CROSS || jt == JoinType.JOIN_INNER) {
return moveCriteriaIntoOnClause(critNode, joinNode);
}
JoinType optimized = JoinUtil.optimizeJoinType(critNode, joinNode, metadata, true);
if (optimized == JoinType.JOIN_INNER) {
moveCriteriaIntoOnClause(critNode, joinNode);
return true; //return true since the join type has changed
}
return false;
}
/**
* @param critNode
* @param joinNode
*/
private boolean moveCriteriaIntoOnClause(PlanNode critNode,
PlanNode joinNode) {
NodeEditor.removeChildNode(critNode.getParent(), critNode);
List joinCriteria = (List)joinNode.getProperty(NodeConstants.Info.JOIN_CRITERIA);
Criteria criteria = (Criteria)critNode.getProperty(NodeConstants.Info.SELECT_CRITERIA);
//since the parser uses EMPTY_LIST, check for size 0 also
if (joinCriteria == null || joinCriteria.size() == 0) {
joinCriteria = new LinkedList();
joinNode.setProperty(NodeConstants.Info.JOIN_CRITERIA, joinCriteria);
}
if (joinCriteria.contains(criteria)) {
return false;
}
boolean moved = false;
if(critNode.hasBooleanProperty(NodeConstants.Info.IS_DEPENDENT_SET)) {
if (criteria instanceof DependentSetCriteria) {
DependentSetCriteria dsc = (DependentSetCriteria)criteria;
if (dsc.hasMultipleAttributes()) {
//split the array based upon the join children.
List<DependentSetCriteria.AttributeComparison> joinExprs = new ArrayList<DependentSetCriteria.AttributeComparison>();
List<DependentSetCriteria.AttributeComparison> leftExprs = new ArrayList<DependentSetCriteria.AttributeComparison>();
List<DependentSetCriteria.AttributeComparison> rightExprs = new ArrayList<DependentSetCriteria.AttributeComparison>();
PlanNode leftJoinSource = FrameUtil.findJoinSourceNode(joinNode.getFirstChild());
PlanNode rightJoinSource = FrameUtil.findJoinSourceNode(joinNode.getLastChild());
for (int i = 0; i < dsc.getAttributes().size(); i++) {
DependentSetCriteria.AttributeComparison comp = dsc.getAttributes().get(i);
Set<GroupSymbol> groups = GroupsUsedByElementsVisitor.getGroups(comp.dep);
if (leftJoinSource.getGroups().containsAll(groups)) {
leftExprs.add(comp);
} else if (rightJoinSource.getGroups().containsAll(groups)){
rightExprs.add(comp);
} else {
joinExprs.add(comp);
}
}
criteria = RuleChooseDependent.createDependentSetCriteria(dsc.getContextSymbol(), joinExprs);
PlanNode left = RuleChooseDependent.createDependentSetNode(dsc.getContextSymbol(), leftExprs);
if (left != null) {
moved = true;
joinNode.getFirstChild().addAsParent(left);
}
PlanNode right = RuleChooseDependent.createDependentSetNode(dsc.getContextSymbol(), rightExprs);
if (right != null) {
moved = true;
joinNode.getLastChild().addAsParent(right);
}
}
}
if (criteria != null) {
joinNode.setProperty(NodeConstants.Info.IS_DEPENDENT_SET, Boolean.TRUE);
}
}
if (criteria != null) {
joinCriteria.add(criteria);
}
if (!joinCriteria.isEmpty() && joinNode.getProperty(Info.JOIN_TYPE) == JoinType.JOIN_CROSS) {
joinNode.setProperty(NodeConstants.Info.JOIN_TYPE, JoinType.JOIN_INNER);
}
return moved;
}
/**
*
* @param critNode
* @param metadata
* @param capFinder
* @throws QueryPlannerException
* @throws QueryMetadataException
* @throws TeiidComponentException
*/
boolean pushTowardOriginatingNode(PlanNode sourceNode, PlanNode critNode, final QueryMetadataInterface metadata, final CapabilitiesFinder capFinder)
throws QueryPlannerException, QueryMetadataException, TeiidComponentException {
boolean groupSelects = sourceNode.getParent().getType() == NodeConstants.Types.SELECT && sourceNode.getChildCount() == 0;
//to keep a stable criteria ordering, move the sourceNode to the top of the criteria chain
while (sourceNode.getParent().getType() == NodeConstants.Types.SELECT) {
sourceNode = sourceNode.getParent();
if (sourceNode == critNode) {
return false;
}
}
// See how far we can move it towards the SOURCE node
final PlanNode destination = examinePath(critNode, sourceNode, metadata, capFinder);
boolean result = false;
if (createdNodes == null & destination.getType() == NodeConstants.Types.ACCESS && isDependentFinalDestination(critNode, destination)) {
Criteria crit = (Criteria)critNode.getProperty(NodeConstants.Info.SELECT_CRITERIA);
if (isMultiAttributeDependentSet(crit)) {
result = splitSet(critNode, new DependentNodeTest() {
@Override
public boolean isValid(PlanNode copyNode) throws QueryMetadataException,
QueryPlannerException, TeiidComponentException {
return RuleRaiseAccess.canRaiseOverSelect(destination, metadata, capFinder, copyNode, null);
}
}, (DependentSetCriteria)crit, destination);
}
}
NodeEditor.removeChildNode(critNode.getParent(), critNode);
destination.addAsParent(critNode);
if (groupSelects && destination == sourceNode && !critNode.hasBooleanProperty(Info.IS_TEMPORARY) && !destination.hasBooleanProperty(Info.IS_TEMPORARY)) {
//Help with the detection of composite keys in pushed criteria
RuleMergeCriteria.mergeChain(critNode, metadata);
}
return result;
}
private interface DependentNodeTest {
boolean isValid(PlanNode copyNode) throws QueryMetadataException, QueryPlannerException, TeiidComponentException;
}
private boolean splitSet(PlanNode critNode, DependentNodeTest test, DependentSetCriteria dscOrig, PlanNode destination)
throws QueryMetadataException, TeiidComponentException,
QueryPlannerException {
boolean result = false;
List<DependentSetCriteria> dscList = splitDependentSetCriteria(dscOrig, false, null);
List<DependentSetCriteria.AttributeComparison> pushable = new ArrayList<AttributeComparison>();
List<DependentSetCriteria.AttributeComparison> nonPushable = new ArrayList<AttributeComparison>();
for (DependentSetCriteria dsc : dscList) {
PlanNode copyNode = copyNode(critNode);
setCriteria(dsc, copyNode);
if (test.isValid(copyNode)) {
pushable.add(dsc.getAttributes().get(0));
} else {
nonPushable.add(dsc.getAttributes().get(0));
}
}
if (!pushable.isEmpty()) {
result = true; //signal that we should run again
if (nonPushable.isEmpty()) {
throw new AssertionError("should not be completely pushed"); //$NON-NLS-1$
}
setCriteria(RuleChooseDependent.createDependentSetCriteria(dscOrig.getContextSymbol(), nonPushable), critNode);
PlanNode copyNode = copyNode(critNode);
setCriteria(RuleChooseDependent.createDependentSetCriteria(dscOrig.getContextSymbol(), pushable), copyNode);
destination.addAsParent(copyNode); //it should be pushed in the next run
}
return result;
}
private void setCriteria(DependentSetCriteria dsc, PlanNode copyNode) {
copyNode.setProperty(NodeConstants.Info.SELECT_CRITERIA, dsc);
copyNode.getGroups().clear();
copyNode.addGroups(GroupsUsedByElementsVisitor.getGroups(dsc));
}
/**
* Examine the path from crit node to source node to determine how far down a node
* can be pushed.
* @return destinationChild
*/
PlanNode examinePath(PlanNode critNode, PlanNode sourceNode, QueryMetadataInterface metadata, CapabilitiesFinder capFinder)
throws QueryPlannerException, TeiidComponentException {
// Walk from source node up to critNode to build list of intervening nodes
Stack<PlanNode> path = new Stack<PlanNode>();
PlanNode currentNode = sourceNode.getParent();
while(currentNode != critNode) {
path.push(currentNode);
currentNode = currentNode.getParent();
}
// Examine path in reverse order (by popping stack)
while(! path.empty()) {
currentNode = path.pop();
// Look for situations where we don't allow SELECT to be pushed
switch (currentNode.getType()) {
case NodeConstants.Types.ACCESS:
try {
if (!RuleRaiseAccess.canRaiseOverSelect(currentNode, metadata, capFinder, critNode, null)) {
return currentNode;
}
if (!RuleRaiseAccess.checkConformedSubqueries(currentNode, critNode, this.createdNodes == null)) {
return currentNode;
}
if (this.createdNodes == null) {
satisfyConditions(critNode, currentNode, metadata);
}
if (isDependentFinalDestination(critNode, currentNode)) {
//once a dependent crit node is pushed, don't bother pushing it further into the command
//dependent access node will use this as an assumption for where dependent sets can appear in the command
markDependent(critNode,currentNode, metadata, capFinder);
return currentNode.getFirstChild();
}
} catch(QueryMetadataException e) {
throw new QueryPlannerException(QueryPlugin.Event.TEIID30267, e, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30267, currentNode.getGroups()));
}
break;
case NodeConstants.Types.JOIN:
//pushing below a join is not necessary under an access node
if (this.createdNodes == null && NodeEditor.findParent(currentNode, NodeConstants.Types.ACCESS) != null) {
return currentNode;
}
// Check whether this criteria is on the inner side of an outer join.
// If so, can't push past the join
JoinType jt = JoinUtil.getJoinTypePreventingCriteriaOptimization(currentNode, critNode);
if(jt != null) {
//if we successfully optimized then this should no longer inhibit the criteria from being pushed
//since the criteria must then be on the outer side of an outer join or on either side of an inner join
JoinType optimized = JoinUtil.optimizeJoinType(critNode, currentNode, metadata, this.createdNodes == null);
if (optimized == null || optimized.isOuter()) {
return currentNode;
}
}
if (this.createdNodes == null) {
satisfyConditions(critNode, currentNode, metadata);
}
break;
default:
if (FrameUtil.isOrderedOrStrictLimit(currentNode)) {
return currentNode;
}
}
}
return sourceNode;
}
private boolean isMultiAttributeDependentSet(Criteria crit) {
return crit instanceof DependentSetCriteria && ((DependentSetCriteria)crit).hasMultipleAttributes();
}
private boolean isDependentFinalDestination(PlanNode critNode,
PlanNode currentNode) {
return critNode.hasBooleanProperty(NodeConstants.Info.IS_DEPENDENT_SET)
&& NodeEditor.findNodePreOrder(currentNode.getFirstChild(), NodeConstants.Types.GROUP, NodeConstants.Types.SOURCE) == null;
}
private void markDependent(PlanNode critNode, PlanNode accessNode, QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException {
//once a dependent crit node is pushed, don't bother pushing it further into the command
//dependent access node will use this as an assumption for where dependent sets can appear in the command
critNode.setProperty(NodeConstants.Info.IS_PUSHED, Boolean.TRUE);
if (createdNodes != null) {
return; //this is during a planning run and should not cause additional side-effects
}
accessNode.setProperty(NodeConstants.Info.IS_DEPENDENT_SET, Boolean.TRUE);
Criteria crit = (Criteria) critNode.getProperty(NodeConstants.Info.SELECT_CRITERIA);
if (isMultiAttributeDependentSet(crit)) {
//split the criteria as needed
List<DependentSetCriteria> crits = splitDependentSetCriteria((DependentSetCriteria) crit, CapabilitiesUtil.supports(Capability.ARRAY_TYPE, RuleRaiseAccess.getModelIDFromAccess(accessNode, metadata), metadata, capFinder), metadata);
critNode.setProperty(NodeConstants.Info.SELECT_CRITERIA, new CompoundCriteria(crits));
}
Collection<ElementSymbol> elements = null;
for (PlanNode joinNode : NodeEditor.findAllNodes(accessNode, NodeConstants.Types.JOIN, NodeConstants.Types.SOURCE)) {
List<Criteria> joinCriteria = (List<Criteria>) joinNode.getProperty(Info.JOIN_CRITERIA);
if (joinCriteria == null) {
continue;
}
for (Criteria joinPredicate : joinCriteria) {
if (!(joinPredicate instanceof CompareCriteria)) {
continue;
}
CompareCriteria cc = (CompareCriteria)joinPredicate;
if (!cc.isOptional()) {
continue;
}
if (elements == null) {
elements = ElementCollectorVisitor.getElements((LanguageObject)critNode.getProperty(Info.SELECT_CRITERIA), true);
}
if (!Collections.disjoint(elements, ElementCollectorVisitor.getElements(cc, false))) {
cc.setOptional(false);
}
}
}
}
private List<DependentSetCriteria> splitDependentSetCriteria(DependentSetCriteria dsc, boolean supportsArray, QueryMetadataInterface metadata) throws QueryMetadataException, TeiidComponentException {
List<AttributeComparison> attributes = dsc.getAttributes();
List<DependentSetCriteria> crits = new ArrayList<DependentSetCriteria>(attributes.size());
Map<Object, List<AttributeComparison>> splits = new LinkedHashMap<Object, List<AttributeComparison>>();
for (int i = 0; i < attributes.size(); i++) {
Object key = null;
DependentSetCriteria.AttributeComparison comp = attributes.get(i);
if (supportsArray && (comp.dep instanceof ElementSymbol)) {
ElementSymbol es = (ElementSymbol)comp.dep;
GroupSymbol group = es.getGroupSymbol();
if (!metadata.isVirtualGroup(group.getMetadataID())) {
key = group;
}
//TODO: we could try to determine further if this is allowable
//for now since we are pushing as far as we can, this is a good indication,
//that it will need split
}
if (key == null) {
key = splits.size();
}
List<AttributeComparison> comps = splits.get(key);
if (comps == null) {
comps = new ArrayList<DependentSetCriteria.AttributeComparison>(2);
splits.put(key, comps);
}
comps.add(comp);
}
for (List<AttributeComparison> comps : splits.values()) {
DependentSetCriteria crit = RuleChooseDependent.createDependentSetCriteria(dsc.getContextSymbol(), comps);
crit.setMakeDepOptions(dsc.getMakeDepOptions());
crits.add(crit);
}
return crits;
}
Boolean pushAcrossFrame(PlanNode sourceNode, PlanNode critNode, QueryMetadataInterface metadata)
throws QueryPlannerException, QueryMetadataException, TeiidComponentException {
//ensure that the criteria can be pushed further
if (sourceNode.getChildCount() == 1 && FrameUtil.isOrderedOrStrictLimit(sourceNode.getFirstChild())) {
return false;
}
//check to see if this is a move across a union
if (sourceNode.getChildCount() > 0) {
PlanNode child = sourceNode.getFirstChild();
child = FrameUtil.findOriginatingNode(child, child.getGroups());
if (child != null && child.getType() == NodeConstants.Types.SET_OP) {
//only allow criteria without subqueires - node cloning doesn't allow for the proper creation of
//multiple nodes with the same subqueries
if (child == sourceNode.getFirstChild() && critNode.getSubqueryContainers().isEmpty()) {
return pushAcrossSetOp(critNode, child, metadata);
}
//this could be an access node in the middle of the source and set op,
//it is an odd case that is not supported for now
return false;
}
}
// See if we can move it towards the SOURCE node
return moveNodeAcrossFrame(critNode, sourceNode, metadata);
}
/**
* All nodes between critNode and sourceNode must be SELECT nodes.
*/
boolean atBoundary(PlanNode critNode, PlanNode sourceNode) {
// Walk from source node to critNode to check each intervening node
PlanNode currentNode = sourceNode.getParent();
while(currentNode != critNode) {
if(currentNode.getType() != NodeConstants.Types.SELECT) {
return false;
}
currentNode = currentNode.getParent();
}
return true;
}
Boolean moveNodeAcrossFrame(PlanNode critNode, PlanNode sourceNode, final QueryMetadataInterface metadata)
throws QueryPlannerException, QueryMetadataException, TeiidComponentException {
// Check that sourceNode has a child to push across
if(sourceNode.getChildCount() == 0) {
return false;
}
final PlanNode projectNode = NodeEditor.findNodePreOrder(sourceNode.getFirstChild(), NodeConstants.Types.PROJECT, NodeConstants.Types.SOURCE);
if(FrameUtil.isProcedure(projectNode)) {
return false;
}
final SymbolMap symbolMap = (SymbolMap) sourceNode.getProperty(NodeConstants.Info.SYMBOL_MAP);
final GroupSymbol sourceGroup = sourceNode.getGroups().iterator().next();
if (!placeConvertedSelectNode(critNode, sourceGroup, projectNode, symbolMap, metadata)) {
if (createdNodes == null) {
Criteria crit = (Criteria)critNode.getProperty(NodeConstants.Info.SELECT_CRITERIA);
if (isMultiAttributeDependentSet(crit) && splitSet(critNode, new DependentNodeTest() {
@Override
public boolean isValid(PlanNode copyNode) throws QueryMetadataException,
QueryPlannerException, TeiidComponentException {
return createConvertedSelectNode(copyNode, sourceGroup, projectNode, symbolMap, metadata) != null;
}
}, (DependentSetCriteria)crit, sourceNode)) {
return null;
}
}
return false;
}
if (createdNodes == null) {
satisfyConditions(critNode, sourceNode, metadata);
}
// Mark critNode as a "phantom"
critNode.setProperty(NodeConstants.Info.IS_PHANTOM, Boolean.TRUE);
return true;
}
/**
* @param critNode
* @param sourceNode
* @throws TeiidComponentException
* @throws QueryMetadataException
*/
static void satisfyConditions(PlanNode critNode,
PlanNode sourceNode, QueryMetadataInterface metadata) throws QueryMetadataException, TeiidComponentException {
List aps = (List)sourceNode.getProperty(NodeConstants.Info.ACCESS_PATTERNS);
Criteria crit = (Criteria)critNode.getProperty(NodeConstants.Info.SELECT_CRITERIA);
if (sourceNode.hasBooleanProperty(Info.IS_MULTI_SOURCE) && crit instanceof CompareCriteria) {
CompareCriteria cc = (CompareCriteria)crit;
if (cc.getLeftExpression() instanceof ElementSymbol && cc.getRightExpression() instanceof Constant) {
ElementSymbol es = (ElementSymbol)cc.getLeftExpression();
if (metadata.isMultiSourceElement(es.getMetadataID())) {
sourceNode.setProperty(Info.IS_MULTI_SOURCE, false);
sourceNode.setProperty(Info.SOURCE_NAME, ((Constant)cc.getRightExpression()).getValue());
}
}
}
if (aps == null) {
return;
}
Collection<ElementSymbol> elements = getElementsIncriteria(crit);
boolean removeAps = satisfyAccessPatterns(aps, elements);
if (removeAps) {
sourceNode.removeProperty(NodeConstants.Info.ACCESS_PATTERNS);
return;
}
Collections.sort(aps);
}
static Collection<ElementSymbol> getElementsIncriteria(Criteria crit) {
Collection<ElementSymbol> elements = new HashSet<ElementSymbol>();
boolean first = true;
if(crit instanceof CompoundCriteria) {
CompoundCriteria compCrit = (CompoundCriteria) crit;
for(Criteria subCrit : compCrit.getCriteria()) {
if(compCrit.getOperator() == CompoundCriteria.AND || first) {
first = false;
elements.addAll(getElementsIncriteria(subCrit));
} else {
elements.retainAll(getElementsIncriteria(subCrit));
}
}
} else {
elements.addAll(ElementCollectorVisitor.getElements(crit, true));
}
return elements;
}
/**
* @param aps
* @param elements
* @return
*/
static boolean satisfyAccessPatterns(List<AccessPattern> aps, Collection<ElementSymbol> elements) {
for (AccessPattern ap : aps) {
ap.getUnsatisfied().removeAll(elements);
if (ap.getUnsatisfied().isEmpty()) {
return true;
}
}
return false;
}
PlanNode copyNode(PlanNode critNode) {
// Create new copy node
PlanNode copyNode = NodeFactory.getNewNode(NodeConstants.Types.SELECT);
// Copy criteria
Criteria crit = (Criteria) critNode.getProperty(NodeConstants.Info.SELECT_CRITERIA);
Criteria copyCrit = (Criteria) crit.clone();
copyNode.setProperty(NodeConstants.Info.SELECT_CRITERIA, copyCrit);
copyNode.addGroups(critNode.getGroups());
if(critNode.hasBooleanProperty(NodeConstants.Info.IS_DEPENDENT_SET)) {
copyNode.setProperty(NodeConstants.Info.IS_DEPENDENT_SET, Boolean.TRUE);
}
if (critNode.hasBooleanProperty(NodeConstants.Info.IS_TEMPORARY)) {
copyNode.setProperty(NodeConstants.Info.IS_TEMPORARY, Boolean.TRUE);
}
if (createdNodes != null) {
createdNodes.add(copyNode);
}
return copyNode;
}
boolean pushAcrossSetOp(PlanNode critNode, PlanNode setOp, QueryMetadataInterface metadata)
throws QueryPlannerException, QueryMetadataException, TeiidComponentException {
// Find source node above union and grab the symbol map
PlanNode sourceNode = NodeEditor.findParent(setOp, NodeConstants.Types.SOURCE);
GroupSymbol virtualGroup = sourceNode.getGroups().iterator().next();
if (createdNodes == null) {
satisfyConditions(critNode, sourceNode, metadata);
}
SymbolMap symbolMap = (SymbolMap) sourceNode.getProperty(NodeConstants.Info.SYMBOL_MAP);
SymbolMap childMap = symbolMap;
// Move criteria to first child of union - names are the same, so no symbol mapping
LinkedList<PlanNode> unionChildren = new LinkedList<PlanNode>();
collectUnionChildren(setOp, unionChildren);
int movedCount = 0;
for (PlanNode planNode : unionChildren) {
// Find first project node
PlanNode projectNode = NodeEditor.findNodePreOrder(planNode, NodeConstants.Types.PROJECT);
if (childMap == null) {
childMap = SymbolMap.createSymbolMap(symbolMap.getKeys(), (List) projectNode.getProperty(NodeConstants.Info.PROJECT_COLS));
}
// Move the node
if(placeConvertedSelectNode(critNode, virtualGroup, projectNode, childMap, metadata)) {
movedCount++;
}
childMap = null; //create a new symbol map for the other children
}
//TODO - the logic here could be made more intelligent about EXCEPT and INTERSECT.
if(movedCount == unionChildren.size()) {
critNode.setProperty(NodeConstants.Info.IS_PHANTOM, Boolean.TRUE);
return true;
}
//otherwise mark it as pushed so we don't consider it again
critNode.setProperty(NodeConstants.Info.IS_PUSHED, Boolean.TRUE);
//if any moved, then we need to continue
return movedCount != 0;
}
static void collectUnionChildren(PlanNode unionNode, List<PlanNode> unionChildren) {
for (PlanNode child : unionNode.getChildren()) {
if(child.getType() == NodeConstants.Types.SET_OP) {
collectUnionChildren(child, unionChildren);
} else {
unionChildren.add(child);
}
}
}
private boolean placeConvertedSelectNode(PlanNode critNode,
GroupSymbol sourceGroup,
PlanNode projectNode,
SymbolMap symbolMap,
QueryMetadataInterface metadata) throws QueryPlannerException {
PlanNode copyNode = createConvertedSelectNode(critNode, sourceGroup,
projectNode, symbolMap, metadata);
if (copyNode == null) {
return false;
}
PlanNode intermediateParent = NodeEditor.findParent(projectNode, NodeConstants.Types.ACCESS, NodeConstants.Types.SOURCE | NodeConstants.Types.SET_OP);
if (intermediateParent != null) {
intermediateParent.addAsParent(copyNode);
} else {
projectNode.getFirstChild().addAsParent(copyNode);
}
return true;
}
private PlanNode createConvertedSelectNode(PlanNode critNode,
GroupSymbol sourceGroup, PlanNode projectNode, SymbolMap symbolMap,
QueryMetadataInterface metadata) throws QueryPlannerException {
// If projectNode has children, then it is from a SELECT without a FROM and the criteria should not be pushed
if(projectNode.getChildCount() == 0) {
return null;
}
Criteria crit = (Criteria) critNode.getProperty(NodeConstants.Info.SELECT_CRITERIA);
Collection<ElementSymbol> cols = ElementCollectorVisitor.getElements(crit, true);
if (projectNode.hasBooleanProperty(Info.HAS_WINDOW_FUNCTIONS)) {
//we can push iff the predicate is against partitioning columns in all projected window functions
Set<WindowFunction> windowFunctions = RuleAssignOutputElements.getWindowFunctions((List<Expression>) projectNode.getProperty(Info.PROJECT_COLS));
for (WindowFunction windowFunction : windowFunctions) {
WindowSpecification spec = windowFunction.getWindowSpecification();
if (spec.getPartition() == null) {
return null;
}
for (ElementSymbol col : cols) {
if (!spec.getPartition().contains(symbolMap.getMappedExpression(col))) {
return null;
}
}
}
}
Boolean conversionResult = checkConversion(symbolMap, cols);
if (conversionResult == Boolean.FALSE) {
return null; //not convertable
}
if (!critNode.getSubqueryContainers().isEmpty()
&& checkConversion(symbolMap, critNode.getCorrelatedReferenceElements()) != null) {
return null; //not convertable, or has an aggregate for a correlated reference
}
PlanNode copyNode = copyNode(critNode);
if (conversionResult == Boolean.TRUE) {
copyNode.setProperty(NodeConstants.Info.IS_HAVING, Boolean.TRUE);
}
FrameUtil.convertNode(copyNode, sourceGroup, null, symbolMap.asMap(), metadata, true);
//any proc relational criteria that is not input criteria should stay above the source
if (sourceGroup.isProcedure() && !copyNode.getGroups().isEmpty()) {
if (this.createdNodes != null) {
this.createdNodes.remove(this.createdNodes.size() - 1);
}
return null;
}
return copyNode;
}
private Boolean checkConversion(SymbolMap symbolMap,
Collection<ElementSymbol> elements) {
Boolean result = null;
for (ElementSymbol element : elements) {
Expression converted = symbolMap.getMappedExpression(element);
if(converted == null) {
return false;
}
Collection<SubqueryContainer<?>> scalarSubqueries = ValueIteratorProviderCollectorVisitor.getValueIteratorProviders(converted);
if (!scalarSubqueries.isEmpty()){
return false;
}
if (!ElementCollectorVisitor.getAggregates(converted, false).isEmpty()) {
result = Boolean.TRUE;
}
}
return result;
}
public String toString() {
return "PushSelectCriteria"; //$NON-NLS-1$
}
}