Author: shawkins
Date: 2011-06-27 14:58:27 -0400 (Mon, 27 Jun 2011)
New Revision: 3275
Modified:
branches/7.4.x/engine/src/main/java/org/teiid/query/optimizer/relational/rules/RulePushAggregates.java
branches/7.4.x/engine/src/test/java/org/teiid/query/optimizer/TestAggregatePushdown.java
Log:
TEIID-1656 adding support for pushdown of aggs over unions with grouping expressions
Modified:
branches/7.4.x/engine/src/main/java/org/teiid/query/optimizer/relational/rules/RulePushAggregates.java
===================================================================
---
branches/7.4.x/engine/src/main/java/org/teiid/query/optimizer/relational/rules/RulePushAggregates.java 2011-06-27
16:56:11 UTC (rev 3274)
+++
branches/7.4.x/engine/src/main/java/org/teiid/query/optimizer/relational/rules/RulePushAggregates.java 2011-06-27
18:58:27 UTC (rev 3275)
@@ -119,7 +119,7 @@
PlanNode setOp = child.getFirstChild();
try {
- pushGroupNodeOverUnion(plan, metadata, capFinder, groupNode, child,
groupingExpressions, setOp, context);
+ pushGroupNodeOverUnion(plan, metadata, capFinder, groupNode, child,
groupingExpressions, setOp, context, analysisRecord);
} catch (QueryResolverException e) {
throw new TeiidComponentException(e);
}
@@ -175,16 +175,35 @@
*/
private void pushGroupNodeOverUnion(PlanNode plan,
QueryMetadataInterface metadata, CapabilitiesFinder capFinder,
- PlanNode groupNode, PlanNode child,
- List<SingleElementSymbol> groupingExpressions, PlanNode setOp, CommandContext
context)
+ PlanNode groupNode, PlanNode unionSourceParent,
+ List<SingleElementSymbol> groupingExpressions, PlanNode setOp, CommandContext
context, AnalysisRecord record)
throws TeiidComponentException, QueryMetadataException,
QueryPlannerException, QueryResolverException {
- if (setOp == null || setOp.getType() != NodeConstants.Types.SET_OP ||
setOp.getProperty(NodeConstants.Info.SET_OPERATION) != Operation.UNION) {
- return; //must not be a union
+ if (setOp == null) {
+ return;
}
+ PlanNode intermediateView = null;
+ if (setOp.getType() != NodeConstants.Types.SET_OP) {
+ if (setOp.getType() != NodeConstants.Types.PROJECT) {
+ return;
+ }
+ intermediateView = unionSourceParent;
+ unionSourceParent = setOp.getFirstChild();
+ if (unionSourceParent == null || unionSourceParent.getType() !=
NodeConstants.Types.SOURCE || unionSourceParent.getFirstChild() == null
+ || unionSourceParent.getFirstChild().getType() != NodeConstants.Types.SET_OP ||
unionSourceParent.getFirstChild().getProperty(NodeConstants.Info.SET_OPERATION) !=
Operation.UNION) {
+ return; //not an eligible union
+ }
+ setOp = unionSourceParent.getFirstChild();
+ if (groupingExpressions == null) {
+ return; //shouldn't happen - the view should have been removed
+ }
+ }
+ if (setOp.getProperty(NodeConstants.Info.SET_OPERATION) != Operation.UNION) {
+ return;
+ }
LinkedHashSet<AggregateSymbol> aggregates = collectAggregates(groupNode);
- Map<ElementSymbol, List<Set<Constant>>> partitionInfo =
(Map<ElementSymbol,
List<Set<Constant>>>)child.getProperty(Info.PARTITION_INFO);
+ Map<ElementSymbol, List<Set<Constant>>> partitionInfo =
(Map<ElementSymbol,
List<Set<Constant>>>)unionSourceParent.getProperty(Info.PARTITION_INFO);
//check to see if any aggregate is dependent upon cardinality
boolean cardinalityDependent =
AggregateSymbol.areAggregatesCardinalityDependent(aggregates);
@@ -192,11 +211,34 @@
LinkedList<PlanNode> unionChildren = new LinkedList<PlanNode>();
findUnionChildren(unionChildren, cardinalityDependent, setOp);
- SymbolMap parentMap = (SymbolMap)child.getProperty(NodeConstants.Info.SYMBOL_MAP);
+ SymbolMap parentMap =
(SymbolMap)unionSourceParent.getProperty(NodeConstants.Info.SYMBOL_MAP);
+ List<ElementSymbol> virtualElements = parentMap.getKeys();
+ GroupSymbol virtualGroup = unionSourceParent.getGroups().iterator().next();
+ List<SingleElementSymbol> actualGroupingExpressions = groupingExpressions;
+ if (intermediateView != null) {
+ actualGroupingExpressions = new
ArrayList<SingleElementSymbol>(groupingExpressions.size());
+ SymbolMap viewMap =
(SymbolMap)intermediateView.getProperty(NodeConstants.Info.SYMBOL_MAP);
+ for (SingleElementSymbol ses : groupingExpressions) {
+ Expression ex = viewMap.getMappedExpression((ElementSymbol)ses);
+ SingleElementSymbol newCol = null;
+ if (ex instanceof SingleElementSymbol) {
+ newCol = (SingleElementSymbol)ex;
+ } else {
+ newCol = new ExpressionSymbol("grouping", ex); //$NON-NLS-1$
+ }
+ actualGroupingExpressions.add(newCol);
+ }
+ }
+
//partitioned union
- if (partitionInfo != null && !Collections.disjoint(partitionInfo.keySet(),
groupingExpressions)) {
- decomposeGroupBy(groupNode, child, groupingExpressions, aggregates, unionChildren,
parentMap, context, metadata, capFinder);
+ if (partitionInfo != null && !Collections.disjoint(partitionInfo.keySet(),
actualGroupingExpressions)) {
+ if (intermediateView != null) {
+ parentMap = pushGroupByView(plan, metadata, capFinder, unionSourceParent,
+ setOp, intermediateView, cardinalityDependent,
+ unionChildren, virtualElements, virtualGroup);
+ }
+ decomposeGroupBy(groupNode, unionSourceParent, groupingExpressions, aggregates,
unionChildren, parentMap, metadata, capFinder, intermediateView != null);
return;
}
@@ -218,15 +260,15 @@
return;
}
- List<ElementSymbol> virtualElements = parentMap.getKeys();
List<SingleElementSymbol> copy = new
ArrayList<SingleElementSymbol>(aggregates);
aggregates.clear();
Map<AggregateSymbol, Expression> aggMap = buildAggregateMap(copy, metadata,
aggregates);
boolean shouldPushdown = false;
List<Boolean> pushdownList = new ArrayList<Boolean>(unionChildren.size());
+
for (PlanNode planNode : unionChildren) {
- boolean pushdown = canPushGroupByToUnionChild(metadata, capFinder,
groupingExpressions, aggregates, planNode);
+ boolean pushdown = canPushGroupByToUnionChild(metadata, capFinder,
actualGroupingExpressions, aggregates, planNode, record);
pushdownList.add(pushdown);
shouldPushdown |= pushdown;
}
@@ -235,17 +277,70 @@
return;
}
+ if (intermediateView != null) {
+ parentMap = pushGroupByView(plan, metadata, capFinder, unionSourceParent,
+ setOp, intermediateView, cardinalityDependent,
+ unionChildren, virtualElements, virtualGroup);
+ virtualElements = parentMap.getKeys();
+ virtualGroup = unionSourceParent.getGroups().iterator().next();
+ }
+
Iterator<Boolean> pushdownIterator = pushdownList.iterator();
for (PlanNode planNode : unionChildren) {
- addView(plan, planNode, pushdownIterator.next(), groupingExpressions, aggregates,
virtualElements, metadata, capFinder);
+ addView(plan, planNode, pushdownIterator.next(), new GroupSymbol("X"),
groupingExpressions, aggregates, virtualElements, metadata, capFinder, null);
//$NON-NLS-1$
}
//update the parent plan with the staged aggregates and the new projected symbols
- List<SingleElementSymbol> projectedViewSymbols =
(List<SingleElementSymbol>)NodeEditor.findNodePreOrder(child,
NodeConstants.Types.PROJECT).getProperty(NodeConstants.Info.PROJECT_COLS);
- List<ElementSymbol> updatedVirturalElement = new
ArrayList<ElementSymbol>(virtualElements);
+ List<SingleElementSymbol> projectedViewSymbols =
(List<SingleElementSymbol>)NodeEditor.findNodePreOrder(unionSourceParent,
NodeConstants.Types.PROJECT).getProperty(NodeConstants.Info.PROJECT_COLS);
//hack to introduce aggregate symbols to the parent view TODO: this should change the
metadata properly.
- GroupSymbol virtualGroup = child.getGroups().iterator().next();
+ SymbolMap newParentMap = modifyUnionSourceParent(unionSourceParent, virtualGroup,
projectedViewSymbols, virtualElements);
+ Map<AggregateSymbol, ElementSymbol> projectedMap = new
HashMap<AggregateSymbol, ElementSymbol>();
+ Iterator<AggregateSymbol> aggIter = aggregates.iterator();
+ for (ElementSymbol projectedViewSymbol :
newParentMap.getKeys().subList(projectedViewSymbols.size() - aggregates.size(),
projectedViewSymbols.size())) {
+ projectedMap.put(aggIter.next(), projectedViewSymbol);
+ }
+ for (Expression expr : aggMap.values()) {
+ ExpressionMappingVisitor.mapExpressions(expr, projectedMap);
+ }
+ mapExpressions(groupNode.getParent(), aggMap, metadata);
+ }
+
+ private SymbolMap pushGroupByView(PlanNode plan,
+ QueryMetadataInterface metadata, CapabilitiesFinder capFinder,
+ PlanNode unionSourceParent, PlanNode setOp,
+ PlanNode intermediateView, boolean cardinalityDependent,
+ LinkedList<PlanNode> unionChildren,
+ List<ElementSymbol> virtualElements, GroupSymbol virtualGroup)
+ throws TeiidComponentException, QueryPlannerException,
+ QueryResolverException {
+ //perform view pushing
+ /*
+ * TODO: this introduces yet another potentially unneeded view, but cannot be removed
by the normal merge virtual logic
+ * due to an intervening access node
+ */
+ PlanNode intermediateProject = intermediateView.getFirstChild();
+ List<SingleElementSymbol> projectedViewSymbols =
(List<SingleElementSymbol>)intermediateProject.getProperty(NodeConstants.Info.PROJECT_COLS);
+ for (PlanNode planNode : unionChildren) {
+ addView(plan, planNode, false, virtualGroup.clone(), null, Collections.EMPTY_SET,
virtualElements, metadata, capFinder, LanguageObject.Util.deepClone(projectedViewSymbols,
SingleElementSymbol.class));
+ }
+ unionChildren.clear();
+ findUnionChildren(unionChildren, cardinalityDependent, setOp);
+ virtualGroup = intermediateView.getGroups().iterator().next();
+ unionSourceParent.getGroups().clear();
+ unionSourceParent.addGroup(virtualGroup);
+ projectedViewSymbols =
(List<SingleElementSymbol>)NodeEditor.findNodePreOrder(unionSourceParent,
NodeConstants.Types.PROJECT).getProperty(NodeConstants.Info.PROJECT_COLS);
+ SymbolMap parentMap = modifyUnionSourceParent(unionSourceParent, virtualGroup,
projectedViewSymbols, Collections.EMPTY_LIST);
+ //remove the old view
+ NodeEditor.removeChildNode(intermediateView, intermediateProject);
+ NodeEditor.removeChildNode(intermediateView.getParent(), intermediateView);
+ return parentMap;
+ }
+
+ private SymbolMap modifyUnionSourceParent(PlanNode unionSourceParent,
+ GroupSymbol virtualGroup,
+ List<SingleElementSymbol> projectedViewSymbols, List<ElementSymbol>
baseVirtualElements) {
+ List<ElementSymbol> updatedVirturalElement = new
ArrayList<ElementSymbol>(baseVirtualElements);
for (int i = updatedVirturalElement.size(); i < projectedViewSymbols.size(); i++) {
SingleElementSymbol symbol = projectedViewSymbols.get(i);
String name = symbol.getShortName();
@@ -257,22 +352,15 @@
updatedVirturalElement.add(virtualElement);
}
SymbolMap newParentMap = SymbolMap.createSymbolMap(updatedVirturalElement,
projectedViewSymbols);
- child.setProperty(NodeConstants.Info.SYMBOL_MAP, newParentMap);
- Map<AggregateSymbol, ElementSymbol> projectedMap = new
HashMap<AggregateSymbol, ElementSymbol>();
- Iterator<AggregateSymbol> aggIter = aggregates.iterator();
- for (ElementSymbol projectedViewSymbol :
newParentMap.getKeys().subList(projectedViewSymbols.size() - aggregates.size(),
projectedViewSymbols.size())) {
- projectedMap.put(aggIter.next(), projectedViewSymbol);
- }
- for (Expression expr : aggMap.values()) {
- ExpressionMappingVisitor.mapExpressions(expr, projectedMap);
- }
- mapExpressions(groupNode.getParent(), aggMap, metadata);
+ unionSourceParent.setProperty(NodeConstants.Info.SYMBOL_MAP, newParentMap);
+ return newParentMap;
}
private void decomposeGroupBy(PlanNode groupNode, PlanNode sourceNode,
List<SingleElementSymbol> groupingExpressions,
LinkedHashSet<AggregateSymbol> aggregates,
- LinkedList<PlanNode> unionChildren, SymbolMap parentMap, CommandContext context,
QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws
QueryPlannerException, QueryMetadataException, TeiidComponentException {
+ LinkedList<PlanNode> unionChildren, SymbolMap parentMap, QueryMetadataInterface
metadata,
+ CapabilitiesFinder capFinder, boolean hadIntermediateView) throws
QueryPlannerException, QueryMetadataException, TeiidComponentException {
// remove the group node
groupNode.getParent().replaceChild(groupNode, groupNode.getFirstChild());
GroupSymbol group = sourceNode.getGroups().iterator().next().clone();
@@ -302,6 +390,10 @@
groupClone.addAsParent(projectPlanNode);
+ if (hadIntermediateView) {
+ //drill down to the possible access node
+ planNode = planNode.getFirstChild().getFirstChild();
+ }
if (planNode.getType() == NodeConstants.Types.ACCESS) {
//TODO: temporarily remove the access node so that the inline view could be
removed if possible
while (RuleRaiseAccess.raiseAccessNode(planNode, planNode, metadata, capFinder,
true, null) != null) {
@@ -319,7 +411,7 @@
private boolean canPushGroupByToUnionChild(QueryMetadataInterface metadata,
CapabilitiesFinder capFinder,
List<SingleElementSymbol> groupingExpressions,
- LinkedHashSet<AggregateSymbol> aggregates, PlanNode planNode)
+ LinkedHashSet<AggregateSymbol> aggregates, PlanNode planNode, AnalysisRecord
record)
throws QueryMetadataException, TeiidComponentException {
if (planNode.getType() != NodeConstants.Types.ACCESS) {
return false;
@@ -334,8 +426,16 @@
return false;
}
}
- if ((groupingExpressions == null || groupingExpressions.isEmpty()) &&
!CapabilitiesUtil.supports(Capability.QUERY_AGGREGATES_COUNT_STAR, modelId, metadata,
capFinder)) {
- return false;
+ if ((groupingExpressions == null || groupingExpressions.isEmpty())) {
+ if (!CapabilitiesUtil.supports(Capability.QUERY_AGGREGATES_COUNT_STAR, modelId,
metadata, capFinder)) {
+ return false;
+ }
+ } else {
+ for (SingleElementSymbol ses : groupingExpressions) {
+ if(! CriteriaCapabilityValidatorVisitor.canPushLanguageObject(ses, modelId, metadata,
capFinder, record)) {
+ return false;
+ }
+ }
}
//TODO: check to see if we are distinct
return true;
@@ -366,11 +466,14 @@
return null;
}
- public void addView(PlanNode root, PlanNode unionSource, boolean pushdown,
List<SingleElementSymbol> groupingExpressions,
+ public void addView(PlanNode root, PlanNode unionSource, boolean pushdown, GroupSymbol
group, List<SingleElementSymbol> groupingExpressions,
Set<AggregateSymbol> aggregates, List<ElementSymbol> virtualElements,
- QueryMetadataInterface metadata, CapabilitiesFinder capFinder)
+ QueryMetadataInterface metadata, CapabilitiesFinder capFinder,
List<SingleElementSymbol> actualProject)
throws TeiidComponentException, QueryPlannerException, QueryResolverException {
- PlanNode originalNode = unionSource;
+ PlanNode accessNode = null;
+ if (pushdown) {
+ accessNode = NodeEditor.findNodePreOrder(unionSource, NodeConstants.Types.ACCESS);
+ }
//branches other than the first need to have their projected column names updated
PlanNode sortNode = NodeEditor.findNodePreOrder(unionSource, NodeConstants.Types.SORT,
NodeConstants.Types.SOURCE);
List<SingleElementSymbol> sortOrder = null;
@@ -394,8 +497,6 @@
updateSymbolName(projectCols, i, virtualElem, projectedSymbol);
}
}
- GroupSymbol group = new GroupSymbol("X"); //$NON-NLS-1$
-
PlanNode intermediateView = createView(group, virtualElements, unionSource, metadata);
SymbolMap symbolMap = (SymbolMap)intermediateView.getProperty(Info.SYMBOL_MAP);
unionSource = intermediateView;
@@ -445,25 +546,26 @@
unionSource = projectPlanNode;
//create proper names for the aggregate symbols
- Select select = new Select(projectedViewSymbols);
+ Select select = null;
+ if (actualProject == null) {
+ select = new Select(projectedViewSymbols);
+ } else {
+ select = new Select(actualProject);
+ }
QueryRewriter.makeSelectUnique(select, false);
projectedViewSymbols = select.getProjectedSymbols();
projectPlanNode.setProperty(NodeConstants.Info.PROJECT_COLS,
projectedViewSymbols);
projectPlanNode.addGroup(group);
if (pushdown) {
- while (RuleRaiseAccess.raiseAccessNode(root, originalNode, metadata, capFinder,
true, null) != null) {
+ while (RuleRaiseAccess.raiseAccessNode(root, accessNode, metadata, capFinder,
true, null) != null) {
//continue to raise
}
}
}
static PlanNode createView(GroupSymbol group, List<? extends SingleElementSymbol>
virtualElements, PlanNode child, QueryMetadataInterface metadata) throws
TeiidComponentException {
- PlanNode intermediateView = NodeFactory.getNewNode(NodeConstants.Types.SOURCE);
SymbolMap symbolMap = createSymbolMap(group, virtualElements, child, metadata);
- intermediateView.setProperty(NodeConstants.Info.SYMBOL_MAP, symbolMap);
- child.addAsParent(intermediateView);
- intermediateView.addGroup(group);
- return intermediateView;
+ return RuleDecomposeJoin.createSource(group, child, symbolMap);
}
private static SymbolMap createSymbolMap(GroupSymbol group,
Modified:
branches/7.4.x/engine/src/test/java/org/teiid/query/optimizer/TestAggregatePushdown.java
===================================================================
---
branches/7.4.x/engine/src/test/java/org/teiid/query/optimizer/TestAggregatePushdown.java 2011-06-27
16:56:11 UTC (rev 3274)
+++
branches/7.4.x/engine/src/test/java/org/teiid/query/optimizer/TestAggregatePushdown.java 2011-06-27
18:58:27 UTC (rev 3275)
@@ -898,6 +898,64 @@
}
/**
+ * pushdown won't happen since searched case is not supported
+ */
+ @Test public void testPushDownOverUnionGroupingExpression() throws Exception {
+ FakeCapabilitiesFinder capFinder = new FakeCapabilitiesFinder();
+ BasicSourceCapabilities caps = getAggregateCapabilities();
+ caps.setCapabilitySupport(Capability.QUERY_SEARCHED_CASE, true);
+ capFinder.addCapabilities("pm1", caps); //$NON-NLS-1$
+ capFinder.addCapabilities("pm2", getAggregateCapabilities());
//$NON-NLS-1$
+
+ ProcessorPlan plan = TestOptimizer.helpPlan("select max(e2), case when e1 is
null then 0 else 1 end from (select e1, e2 from pm1.g1 union all select e1, e2 from
pm2.g2) z group by case when e1 is null then 0 else 1 end",
RealMetadataFactory.example1Cached(), null, capFinder, //$NON-NLS-1$
+ new String[]{"SELECT v_1.c_0, MAX(v_1.c_1) FROM (SELECT CASE WHEN
v_0.c_0 IS NULL THEN 0 ELSE 1 END AS c_0, v_0.c_1 FROM (SELECT g_0.e1 AS c_0, g_0.e2 AS
c_1 FROM pm1.g1 AS g_0) AS v_0) AS v_1 GROUP BY v_1.c_0", //$NON-NLS-1$
+ "SELECT g_0.e1, g_0.e2 FROM pm2.g2 AS g_0"},
ComparisonMode.EXACT_COMMAND_STRING); //$NON-NLS-1$
+ TestOptimizer.checkNodeTypes(plan, new int[] {
+ 2, // Access
+ 0, // DependentAccess
+ 0, // DependentSelect
+ 0, // DependentProject
+ 0, // DupRemove
+ 1, // Grouping
+ 0, // NestedLoopJoinStrategy
+ 0, // MergeJoinStrategy
+ 0, // Null
+ 0, // PlanExecution
+ 3, // Project
+ 0, // Select
+ 0, // Sort
+ 1 // UnionAll
+ });
+ }
+
+ @Test public void testPushDownOverUnionGroupingExpressionPartitioned() throws
Exception {
+ FakeCapabilitiesFinder capFinder = new FakeCapabilitiesFinder();
+ BasicSourceCapabilities caps = getAggregateCapabilities();
+ caps.setCapabilitySupport(Capability.QUERY_SEARCHED_CASE, true);
+ capFinder.addCapabilities("pm1", caps); //$NON-NLS-1$
+
+ ProcessorPlan plan = TestOptimizer.helpPlan("select max(e2), case when e1 is
null then 0 else 1 end from (select e1, e2, 1 as part from pm1.g1 union all select e1, e2,
2 as part from pm1.g2) z group by case when e1 is null then 0 else 1 end, part",
RealMetadataFactory.example1Cached(), null, capFinder, //$NON-NLS-1$
+ new String[]{"SELECT MAX(v_1.c_2), v_1.c_0 FROM (SELECT CASE WHEN
v_0.c_0 IS NULL THEN 0 ELSE 1 END AS c_0, v_0.c_1, v_0.c_2 FROM (SELECT g_0.e1 AS c_0, 2
AS c_1, g_0.e2 AS c_2 FROM pm1.g2 AS g_0) AS v_0) AS v_1 GROUP BY v_1.c_0, v_1.c_1",
+ "SELECT MAX(v_1.c_2), v_1.c_0 FROM (SELECT CASE WHEN v_0.c_0 IS NULL THEN 0
ELSE 1 END AS c_0, v_0.c_1, v_0.c_2 FROM (SELECT g_0.e1 AS c_0, 1 AS c_1, g_0.e2 AS c_2
FROM pm1.g1 AS g_0) AS v_0) AS v_1 GROUP BY v_1.c_0, v_1.c_1"},
ComparisonMode.EXACT_COMMAND_STRING); //$NON-NLS-1$
+ TestOptimizer.checkNodeTypes(plan, new int[] {
+ 2, // Access
+ 0, // DependentAccess
+ 0, // DependentSelect
+ 0, // DependentProject
+ 0, // DupRemove
+ 0, // Grouping
+ 0, // NestedLoopJoinStrategy
+ 0, // MergeJoinStrategy
+ 0, // Null
+ 0, // PlanExecution
+ 1, // Project
+ 0, // Select
+ 0, // Sort
+ 1 // UnionAll
+ });
+ }
+
+ /**
* Ensures that we do not raise criteria over a group by
* TODO: check if the criteria only depends on grouping columns
*/