@Override protected AbstractPlanNode recursivelyApply(AbstractPlanNode planNode) { assert (planNode != null); // breadth first: // find AggregatePlanNode with exactly one child // where that child is an AbstractScanPlanNode. // Inline any qualifying AggregatePlanNode to its AbstractScanPlanNode. Queue<AbstractPlanNode> children = new LinkedList<AbstractPlanNode>(); children.add(planNode); while (!children.isEmpty()) { AbstractPlanNode plan = children.remove(); AbstractPlanNode newPlan = inlineAggregationApply(plan); if (newPlan != plan) { if (plan == planNode) { planNode = newPlan; } else { planNode.replaceChild(plan, newPlan); } } for (int i = 0; i < newPlan.getChildCount(); i++) { children.add(newPlan.getChild(i)); } } return planNode; }
AbstractPlanNode inlineAggregationApply(AbstractPlanNode plan) { // check for an aggregation of the right form if ((plan instanceof AggregatePlanNode) == false || (plan instanceof WindowFunctionPlanNode)) { return plan; } assert (plan.getChildCount() == 1); AggregatePlanNode aggplan = (AggregatePlanNode) plan; // Assuming all AggregatePlanNode has not been inlined before this microoptimization AbstractPlanNode child = aggplan.getChild(0); // EE Currently support: seqscan + indexscan if (child.getPlanNodeType() != PlanNodeType.SEQSCAN && child.getPlanNodeType() != PlanNodeType.INDEXSCAN && child.getPlanNodeType() != PlanNodeType.NESTLOOP && child.getPlanNodeType() != PlanNodeType.NESTLOOPINDEX) { return plan; } if (child.getPlanNodeType() == PlanNodeType.INDEXSCAN) { // Currently do not conflict with the optimized MIN/MAX // because of the big amount of tests changed. IndexScanPlanNode isp = (IndexScanPlanNode) child; LimitPlanNode limit = (LimitPlanNode) isp.getInlinePlanNode(PlanNodeType.LIMIT); if (limit != null && (aggplan.isTableMin() || aggplan.isTableMax())) { // Optimized MIN/MAX if (limit.getLimit() == 1 && limit.getOffset() == 0) { return plan; } } } // Inline aggregate node AbstractPlanNode parent = null; if (aggplan.getParentCount() == 1) { parent = aggplan.getParent(0); } child.addInlinePlanNode(aggplan); child.clearParents(); if (parent != null) { parent.replaceChild(aggplan, child); } return child; }
@Override protected AbstractPlanNode recursivelyApply(AbstractPlanNode plan) { assert (plan != null); // depth first: // find AggregatePlanNode with exactly one child // where that child is an AbstractScanPlanNode. // Replace any qualifying AggregatePlanNode / AbstractScanPlanNode pair // with an IndexCountPlanNode or TableCountPlanNode ArrayList<AbstractPlanNode> children = new ArrayList<AbstractPlanNode>(); for (int i = 0; i < plan.getChildCount(); i++) children.add(plan.getChild(i)); for (AbstractPlanNode child : children) { // TODO this will break when children feed multiple parents AbstractPlanNode newChild = recursivelyApply(child); // Do a graft into the (parent) plan only if a replacement for a child was found. if (newChild == child) { continue; } boolean replaced = plan.replaceChild(child, newChild); assert (true == replaced); } // check for an aggregation of the right form if ((plan instanceof AggregatePlanNode) == false) return plan; assert (plan.getChildCount() == 1); AggregatePlanNode aggplan = (AggregatePlanNode) plan; // ENG-6131 fixed here. if (!(aggplan.isTableCountStar() || aggplan.isTableNonDistinctCountConstant() || aggplan.isTableCountNonDistinctNullableColumn())) { return plan; } AbstractPlanNode child = plan.getChild(0); // A table count can replace a seq scan only if it has no predicates. if (child instanceof SeqScanPlanNode) { if (((SeqScanPlanNode) child).getPredicate() != null) { return plan; } AbstractExpression postPredicate = aggplan.getPostPredicate(); if (postPredicate != null) { List<AbstractExpression> aggList = postPredicate.findAllSubexpressionsOfClass(AggregateExpression.class); boolean allCountStar = true; for (AbstractExpression expr : aggList) { if (expr.getExpressionType() != ExpressionType.AGGREGATE_COUNT_STAR) { allCountStar = false; break; } } if (allCountStar) { return plan; } } if (hasInlineLimit(aggplan)) { // table count EE executor does not handle inline limit stuff return plan; } return new TableCountPlanNode((AbstractScanPlanNode) child, aggplan); } // Otherwise, optimized counts only replace particular cases of index scan. if ((child instanceof IndexScanPlanNode) == false) return plan; IndexScanPlanNode isp = (IndexScanPlanNode) child; // Guard against (possible future?) cases of indexable subquery. if (((IndexScanPlanNode) child).isSubQuery()) { return plan; } // An index count or table count can replace an index scan only if it has no (post-)predicates // except those (post-)predicates are artifact predicates we added for reverse scan purpose only if (isp.getPredicate() != null && !isp.isPredicatesOptimizableForAggregate()) { return plan; } // With no start or end keys, there's not much a counting index can do. if (isp.getEndExpression() == null && isp.getSearchKeyExpressions().size() == 0) { // An indexed query without a where clause can fall back to a plain old table count. // This can only happen when a confused query like // "select count(*) from table order by index_key;" // meets a naive planner that doesn't just cull the no-op ORDER BY. Who, us? if (hasInlineLimit(aggplan)) { return plan; } return new TableCountPlanNode(isp, aggplan); } // check for the index's support for counting Index idx = isp.getCatalogIndex(); if (!idx.getCountable()) { return plan; } // The core idea is that counting index needs to know the start key and end key to // jump to to get counts instead of actually doing any scanning. // Options to be determined are: // - whether each of the start/end keys is missing, partial (a prefix of a compund key), or // complete, // - whether the count should include or exclude entries exactly matching each of the start/end // keys. // Not all combinations of these options are supported; // unsupportable cases cause the factory method to return null. IndexCountPlanNode countingPlan = IndexCountPlanNode.createOrNull(isp, aggplan); if (countingPlan == null) { return plan; } return countingPlan; }