--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.sql.compile.GroupByNode\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to you under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+package org.apache.derby.impl.sql.compile;\r
+\r
+import java.util.Iterator;\r
+import java.util.Vector;\r
+import java.util.ArrayList;\r
+import java.util.Comparator;\r
+import java.util.Collections;\r
+\r
+import org.apache.derby.catalog.IndexDescriptor;\r
+import org.apache.derby.iapi.error.StandardException;\r
+import org.apache.derby.iapi.reference.ClassName;\r
+import org.apache.derby.iapi.reference.SQLState;\r
+import org.apache.derby.iapi.services.classfile.VMOpcode;\r
+import org.apache.derby.iapi.services.compiler.MethodBuilder;\r
+import org.apache.derby.iapi.services.io.FormatableArrayHolder;\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+import org.apache.derby.iapi.sql.LanguageFactory;\r
+import org.apache.derby.iapi.sql.ResultColumnDescriptor;\r
+import org.apache.derby.iapi.sql.compile.AccessPath;\r
+import org.apache.derby.iapi.sql.compile.C_NodeTypes;\r
+import org.apache.derby.iapi.sql.compile.CostEstimate;\r
+import org.apache.derby.iapi.sql.compile.Optimizable;\r
+import org.apache.derby.iapi.sql.compile.OptimizablePredicate;\r
+import org.apache.derby.iapi.sql.compile.OptimizablePredicateList;\r
+import org.apache.derby.iapi.sql.compile.Optimizer;\r
+import org.apache.derby.iapi.sql.compile.RequiredRowOrdering;\r
+import org.apache.derby.iapi.sql.compile.RowOrdering;\r
+import org.apache.derby.iapi.sql.dictionary.ConglomerateDescriptor;\r
+import org.apache.derby.iapi.sql.dictionary.DataDictionary;\r
+import org.apache.derby.iapi.store.access.ColumnOrdering;\r
+import org.apache.derby.impl.sql.execute.AggregatorInfo;\r
+import org.apache.derby.impl.sql.execute.AggregatorInfoList;\r
+\r
+\r
+/**\r
+ * A GroupByNode represents a result set for a grouping operation\r
+ * on a select. Note that this includes a SELECT with aggregates\r
+ * and no grouping columns (in which case the select list is null)\r
+ * It has the same description as its input result set.\r
+ * <p>\r
+ * For the most part, it simply delegates operations to its bottomPRSet,\r
+ * which is currently expected to be a ProjectRestrictResultSet generated\r
+ * for a SelectNode.\r
+ * <p>\r
+ * NOTE: A GroupByNode extends FromTable since it can exist in a FromList.\r
+ * <p>\r
+ * There is a lot of room for optimizations here: <UL>\r
+ * <LI> agg(distinct x) group by x => agg(x) group by x (for min and max) </LI>\r
+ * <LI> min()/max() use index scans if possible, no sort may \r
+ * be needed. </LI>\r
+ * </UL>\r
+ *\r
+ *\r
+ */\r
+public class GroupByNode extends SingleChildResultSetNode\r
+{\r
+ /**\r
+ * The GROUP BY list\r
+ */\r
+ GroupByList groupingList;\r
+\r
+ /**\r
+ * The list of all aggregates in the query block\r
+ * that contains this group by.\r
+ */\r
+ Vector aggregateVector;\r
+\r
+ /**\r
+ * Information that is used at execution time to\r
+ * process aggregates.\r
+ */\r
+ private AggregatorInfoList aggInfo;\r
+\r
+ /**\r
+ * The parent to the GroupByNode. If we need to\r
+ * generate a ProjectRestrict over the group by\r
+ * then this is set to that node. Otherwise it\r
+ * is null.\r
+ */\r
+ FromTable parent;\r
+\r
+ private boolean addDistinctAggregate;\r
+ private boolean singleInputRowOptimization;\r
+ private int addDistinctAggregateColumnNum;\r
+\r
+ // Is the source in sorted order\r
+ private boolean isInSortedOrder;\r
+\r
+ private ValueNode havingClause;\r
+ \r
+ private SubqueryList havingSubquerys;\r
+ \r
+ /**\r
+ * Intializer for a GroupByNode.\r
+ *\r
+ * @param bottomPR The child FromTable\r
+ * @param groupingList The groupingList\r
+ * @param aggregateVector The vector of aggregates from\r
+ * the query block. Since aggregation is done\r
+ * at the same time as grouping, we need them\r
+ * here.\r
+ * @param havingClause The having clause.\r
+ * @param havingSubquerys subqueries in the having clause.\r
+ * @param tableProperties Properties list associated with the table\r
+ * @param nestingLevel nestingLevel of this group by node. This is used for \r
+ * error checking of group by queries with having clause.\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public void init(\r
+ Object bottomPR,\r
+ Object groupingList,\r
+ Object aggregateVector,\r
+ Object havingClause,\r
+ Object havingSubquerys,\r
+ Object tableProperties,\r
+ Object nestingLevel)\r
+ throws StandardException\r
+ {\r
+ super.init(bottomPR, tableProperties);\r
+ setLevel(((Integer)nestingLevel).intValue());\r
+ this.havingClause = (ValueNode)havingClause;\r
+ this.havingSubquerys = (SubqueryList)havingSubquerys;\r
+ /* Group by without aggregates gets xformed into distinct */\r
+ if (SanityManager.DEBUG)\r
+ {\r
+// Aggregage vector can be null if we have a having clause.\r
+// select c1 from t1 group by c1 having c1 > 1; \r
+// SanityManager.ASSERT(((Vector) aggregateVector).size() > 0,\r
+// "aggregateVector expected to be non-empty");\r
+ if (!(childResult instanceof Optimizable))\r
+ {\r
+ SanityManager.THROWASSERT("childResult, " + childResult.getClass().getName() +\r
+ ", expected to be instanceof Optimizable");\r
+ }\r
+ if (!(childResult instanceof FromTable))\r
+ {\r
+ SanityManager.THROWASSERT("childResult, " + childResult.getClass().getName() +\r
+ ", expected to be instanceof FromTable");\r
+ }\r
+ }\r
+\r
+ ResultColumnList newBottomRCL;\r
+ this.groupingList = (GroupByList) groupingList;\r
+ this.aggregateVector = (Vector) aggregateVector;\r
+ this.parent = this;\r
+\r
+ /*\r
+ ** The first thing we do is put ourselves on\r
+ ** top of the SELECT. The select becomes the\r
+ ** childResult. So our RCL becomes its RCL (so\r
+ ** nodes above it now point to us). Map our\r
+ ** RCL to its columns.\r
+ */\r
+ newBottomRCL = childResult.getResultColumns().copyListAndObjects();\r
+ resultColumns = childResult.getResultColumns();\r
+ childResult.setResultColumns(newBottomRCL);\r
+\r
+ /*\r
+ ** We have aggregates, so we need to add\r
+ ** an extra PRNode and we also have to muck around\r
+ ** with our trees a might.\r
+ */\r
+ addAggregates();\r
+\r
+ /* We say that the source is never in sorted order if there is a distinct aggregate.\r
+ * (Not sure what happens if it is, so just skip it for now.)\r
+ * Otherwise, we check to see if the source is in sorted order on any permutation\r
+ * of the grouping columns.)\r
+ */\r
+ if (! addDistinctAggregate && groupingList != null)\r
+ {\r
+ ColumnReference[] crs =\r
+ new ColumnReference[this.groupingList.size()];\r
+\r
+ // Now populate the CR array and see if ordered\r
+ int glSize = this.groupingList.size();\r
+ int index;\r
+ for (index = 0; index < glSize; index++)\r
+ {\r
+ GroupByColumn gc =\r
+ (GroupByColumn) this.groupingList.elementAt(index);\r
+ if (gc.getColumnExpression() instanceof ColumnReference) \r
+ {\r
+ crs[index] = (ColumnReference)gc.getColumnExpression();\r
+ } \r
+ else \r
+ {\r
+ isInSortedOrder = false;\r
+ break;\r
+ }\r
+ \r
+ }\r
+ if (index == glSize) {\r
+ isInSortedOrder = childResult.isOrderedOn(crs, true, (Vector)null);\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Get whether or not the source is in sorted order.\r
+ *\r
+ * @return Whether or not the source is in sorted order.\r
+ */\r
+ boolean getIsInSortedOrder()\r
+ {\r
+ return isInSortedOrder;\r
+ }\r
+\r
+ /**\r
+ * Add the extra result columns required by the aggregates\r
+ * to the result list.\r
+ * \r
+ * @exception standard exception\r
+ */\r
+ private void addAggregates()\r
+ throws StandardException\r
+ {\r
+ addNewPRNode();\r
+ addNewColumnsForAggregation();\r
+ addDistinctAggregatesToOrderBy();\r
+ }\r
+\r
+ /**\r
+ * Add any distinct aggregates to the order by list.\r
+ * Asserts that there are 0 or more distincts.\r
+ */\r
+ private void addDistinctAggregatesToOrderBy()\r
+ {\r
+ int numDistinct = numDistinctAggregates(aggregateVector);\r
+ if (numDistinct != 0)\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(numDistinct == 1,\r
+ "Should not have more than 1 distinct aggregate per Group By node");\r
+ }\r
+ \r
+ AggregatorInfo agg = null;\r
+ int count = aggInfo.size();\r
+ for (int i = 0; i < count; i++)\r
+ {\r
+ agg = (AggregatorInfo) aggInfo.elementAt(i);\r
+ if (agg.isDistinct())\r
+ {\r
+ break;\r
+ }\r
+ }\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(agg != null && agg.isDistinct());\r
+ }\r
+\r
+ addDistinctAggregate = true;\r
+ addDistinctAggregateColumnNum = agg.getInputColNum();\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Add a new PR node for aggregation. Put the\r
+ * new PR under the sort.\r
+ * \r
+ * @exception standard exception\r
+ */\r
+ private void addNewPRNode()\r
+ throws StandardException\r
+ {\r
+ /*\r
+ ** Get the new PR, put above the GroupBy. \r
+ */\r
+ ResultColumnList rclNew = (ResultColumnList)getNodeFactory().getNode(\r
+ C_NodeTypes.RESULT_COLUMN_LIST,\r
+ getContextManager());\r
+ int sz = resultColumns.size();\r
+ for (int i = 0; i < sz; i++) \r
+ {\r
+ ResultColumn rc = (ResultColumn) resultColumns.elementAt(i);\r
+ if (!rc.isGenerated()) {\r
+ rclNew.addElement(rc);\r
+ }\r
+ }\r
+\r
+ // if any columns in the source RCL were generated for an order by\r
+ // remember it in the new RCL as well. After the sort is done it will\r
+ // have to be projected out upstream.\r
+ rclNew.copyOrderBySelect(resultColumns);\r
+ \r
+ parent = (FromTable) getNodeFactory().getNode(\r
+ C_NodeTypes.PROJECT_RESTRICT_NODE,\r
+ this, // child\r
+ rclNew,\r
+ null, //havingClause,\r
+ null, // restriction list\r
+ null, // project subqueries\r
+ havingSubquerys,\r
+ tableProperties,\r
+ getContextManager());\r
+\r
+\r
+ /*\r
+ ** Reset the bottom RCL to be empty.\r
+ */\r
+ childResult.setResultColumns((ResultColumnList)\r
+ getNodeFactory().getNode(\r
+ C_NodeTypes.RESULT_COLUMN_LIST,\r
+ getContextManager()));\r
+\r
+ /*\r
+ ** Set the group by RCL to be empty\r
+ */\r
+ resultColumns = (ResultColumnList) getNodeFactory().getNode(\r
+ C_NodeTypes.RESULT_COLUMN_LIST,\r
+ getContextManager());\r
+\r
+ }\r
+\r
+ /**\r
+ * In the query rewrite for group by, add the columns on which\r
+ * we are doing the group by.\r
+\r
+ * @see #addNewColumnsForAggregation\r
+ */\r
+ private void addUnAggColumns() throws StandardException\r
+ {\r
+ ResultColumnList bottomRCL = childResult.getResultColumns();\r
+ ResultColumnList groupByRCL = resultColumns;\r
+\r
+ ArrayList referencesToSubstitute = new ArrayList();\r
+ ArrayList havingRefsToSubstitute = null;\r
+ if (havingClause != null)\r
+ havingRefsToSubstitute = new ArrayList();\r
+ int sz = groupingList.size();\r
+ for (int i = 0; i < sz; i++) \r
+ {\r
+ GroupByColumn gbc = (GroupByColumn) groupingList.elementAt(i);\r
+ ResultColumn newRC = (ResultColumn) getNodeFactory().getNode(\r
+ C_NodeTypes.RESULT_COLUMN,\r
+ "##UnaggColumn",\r
+ gbc.getColumnExpression(),\r
+ getContextManager());\r
+\r
+ // add this result column to the bottom rcl\r
+ bottomRCL.addElement(newRC);\r
+ newRC.markGenerated();\r
+ newRC.bindResultColumnToExpression();\r
+ newRC.setVirtualColumnId(bottomRCL.size());\r
+ \r
+ // now add this column to the groupbylist\r
+ ResultColumn gbRC = (ResultColumn) getNodeFactory().getNode(\r
+ C_NodeTypes.RESULT_COLUMN,\r
+ "##UnaggColumn",\r
+ gbc.getColumnExpression(),\r
+ getContextManager());\r
+ groupByRCL.addElement(gbRC);\r
+ gbRC.markGenerated();\r
+ gbRC.bindResultColumnToExpression();\r
+ gbRC.setVirtualColumnId(groupByRCL.size());\r
+\r
+ /*\r
+ ** Reset the original node to point to the\r
+ ** Group By result set.\r
+ */\r
+ VirtualColumnNode vc = (VirtualColumnNode) getNodeFactory().getNode(\r
+ C_NodeTypes.VIRTUAL_COLUMN_NODE,\r
+ this, // source result set.\r
+ gbRC,\r
+ new Integer(groupByRCL.size()),\r
+ getContextManager());\r
+\r
+ // we replace each group by expression \r
+ // in the projection list with a virtual column node\r
+ // that effectively points to a result column \r
+ // in the result set doing the group by\r
+ //\r
+ // Note that we don't perform the replacements\r
+ // immediately, but instead we accumulate them\r
+ // until the end of the loop. This allows us to\r
+ // sort the expressions and process them in\r
+ // descending order of complexity, necessary\r
+ // because a compound expression may contain a\r
+ // reference to a simple grouped column, but in\r
+ // such a case we want to process the expression\r
+ // as an expression, not as individual column\r
+ // references. E.g., if the statement was:\r
+ // SELECT ... GROUP BY C1, C1 * (C2 / 100), C3\r
+ // then we don't want the replacement of the\r
+ // simple column reference C1 to affect the\r
+ // compound expression C1 * (C2 / 100). DERBY-3094.\r
+ //\r
+ ValueNode vn = gbc.getColumnExpression();\r
+ SubstituteExpressionVisitor vis =\r
+ new SubstituteExpressionVisitor(vn, vc,\r
+ AggregateNode.class);\r
+ referencesToSubstitute.add(vis);\r
+ \r
+ // Since we always need a PR node on top of the GB \r
+ // node to perform projection we can use it to perform \r
+ // the having clause restriction as well. \r
+ // To evaluate the having clause correctly, we need to \r
+ // convert each aggregate and expression to point \r
+ // to the appropriate result column in the group by node. \r
+ // This is no different from the transformations we do to \r
+ // correctly evaluate aggregates and expressions in the \r
+ // projection list. \r
+ // \r
+ //\r
+ // For this query:\r
+ // SELECT c1, SUM(c2), MAX(c3)\r
+ // FROM t1 \r
+ // HAVING c1+max(c3) > 0;\r
+\r
+ // PRSN RCL -> (ptr(gbn:rcl[0]), ptr(gbn:rcl[1]), ptr(gbn:rcl[4]))\r
+ // Restriction: (> (+ ptr(gbn:rcl[0]) ptr(gbn:rcl[4])) 0)\r
+ // |\r
+ // GBN (RCL) -> (C1, SUM(C2), <input>, <aggregator>, MAX(C3), <input>, <aggregator>\r
+ // |\r
+ // FBT (C1, C2)\r
+ if (havingClause != null)\r
+ {\r
+ SubstituteExpressionVisitor havingSE =\r
+ new SubstituteExpressionVisitor(vn,vc,null);\r
+ havingRefsToSubstitute.add(havingSE);\r
+ }\r
+ gbc.setColumnPosition(bottomRCL.size());\r
+ }\r
+ Comparator sorter = new ExpressionSorter();\r
+ Collections.sort(referencesToSubstitute,sorter);\r
+ for (int r = 0; r < referencesToSubstitute.size(); r++)\r
+ parent.getResultColumns().accept(\r
+ (SubstituteExpressionVisitor)referencesToSubstitute.get(r));\r
+ if (havingRefsToSubstitute != null)\r
+ {\r
+ Collections.sort(havingRefsToSubstitute,sorter);\r
+ for (int r = 0; r < havingRefsToSubstitute.size(); r++)\r
+ havingClause.accept(\r
+ (SubstituteExpressionVisitor)havingRefsToSubstitute.get(r));\r
+}\r
+ }\r
+\r
+ /**\r
+ * Add a whole slew of columns needed for \r
+ * aggregation. Basically, for each aggregate we add\r
+ * 3 columns: the aggregate input expression\r
+ * and the aggregator column and a column where the aggregate \r
+ * result is stored. The input expression is\r
+ * taken directly from the aggregator node. The aggregator\r
+ * is the run time aggregator. We add it to the RC list\r
+ * as a new object coming into the sort node.\r
+ * <P>\r
+ * At this point this is invoked, we have the following\r
+ * tree: <UL>\r
+ * PR - (PARENT): RCL is the original select list\r
+ * |\r
+ * PR - GROUP BY: RCL is empty\r
+ * |\r
+ * PR - FROM TABLE: RCL is empty </UL> <P>\r
+ *\r
+ * For each ColumnReference in PR RCL <UL>\r
+ * <LI> clone the ref </LI>\r
+ * <LI> create a new RC in the bottom RCL and set it \r
+ * to the col ref </LI>\r
+ * <LI> create a new RC in the GROUPBY RCL and set it to \r
+ * point to the bottom RC </LI>\r
+ * <LI> reset the top PR ref to point to the new GROUPBY\r
+ * RC</LI></UL> \r
+ *\r
+ * For each aggregate in aggregateVector <UL>\r
+ * <LI> create RC in FROM TABLE. Fill it with \r
+ * aggs Operator.\r
+ * <LI> create RC in FROM TABLE for agg result</LI>\r
+ * <LI> create RC in FROM TABLE for aggregator</LI>\r
+ * <LI> create RC in GROUPBY for agg input, set it\r
+ * to point to FROM TABLE RC </LI>\r
+ * <LI> create RC in GROUPBY for agg result</LI>\r
+ * <LI> create RC in GROUPBY for aggregator</LI>\r
+ * <LI> replace Agg with reference to RC for agg result </LI></UL>.\r
+ * <P>\r
+ * For a query like,\r
+ * <pre>\r
+ select c1, sum(c2), max(c3)\r
+ from t1 \r
+ group by c1;\r
+ </pre>\r
+ * the query tree ends up looking like this:\r
+ <pre>\r
+ ProjectRestrictNode RCL -> (ptr to GBN(column[0]), ptr to GBN(column[1]), ptr to GBN(column[4]))\r
+ |\r
+ GroupByNode RCL->(C1, SUM(C2), <agg-input>, <aggregator>, MAX(C3), <agg-input>, <aggregator>)\r
+ |\r
+ ProjectRestrict RCL->(C1, C2, C3)\r
+ |\r
+ FromBaseTable\r
+ </pre>\r
+ * \r
+ * The RCL of the GroupByNode contains all the unagg (or grouping columns)\r
+ * followed by 3 RC's for each aggregate in this order: the final computed\r
+ * aggregate value, the aggregate input and the aggregator function.\r
+ * <p>\r
+ * The Aggregator function puts the results in the first of the 3 RC's \r
+ * and the PR resultset in turn picks up the value from there.\r
+ * <p>\r
+ * The notation (ptr to GBN(column[0])) basically means that it is\r
+ * a pointer to the 0th RC in the RCL of the GroupByNode. \r
+ * <p>\r
+ * The addition of these unagg and agg columns to the GroupByNode and \r
+ * to the PRN is performed in addUnAggColumns and addAggregateColumns. \r
+ * <p>\r
+ * Note that that addition of the GroupByNode is done after the\r
+ * query is optimized (in SelectNode#modifyAccessPaths) which means a \r
+ * fair amount of patching up is needed to account for generated group by columns.\r
+ * @exception standard exception\r
+ */\r
+ private void addNewColumnsForAggregation()\r
+ throws StandardException\r
+ {\r
+ aggInfo = new AggregatorInfoList();\r
+ if (groupingList != null)\r
+ {\r
+ addUnAggColumns();\r
+ }\r
+ if (havingClause != null) {\r
+ // we have replaced group by expressions in the having clause.\r
+ // there should be no column references in the having clause \r
+ // referencing this table. Skip over aggregate nodes.\r
+ // select a, sum(b) from t group by a having a+c > 1 \r
+ // is not valid because of column c.\r
+ // \r
+ // it is allright to have columns from parent or child subqueries;\r
+ // select * from p where p.p1 in \r
+ // (select c.c1 from c group by c.c1 having count(*) = p.p2\r
+ CollectNodesVisitor collectNodesVisitor = \r
+ new CollectNodesVisitor(ColumnReference.class, AggregateNode.class);\r
+ havingClause.accept(collectNodesVisitor);\r
+ for (Iterator it = collectNodesVisitor.getList().iterator();\r
+ it.hasNext(); ) \r
+ {\r
+ ColumnReference cr = (ColumnReference)it.next();\r
+ \r
+ if (!cr.getGeneratedToReplaceAggregate() && \r
+ cr.getSourceLevel() == level) {\r
+ throw StandardException.newException(\r
+ SQLState.LANG_INVALID_COL_HAVING_CLAUSE, \r
+ cr.getSQLColumnName()); \r
+ }\r
+ }\r
+ }\r
+ addAggregateColumns();\r
+ }\r
+ \r
+ /**\r
+ * In the query rewrite involving aggregates, add the columns for\r
+ * aggregation.\r
+ *\r
+ * @see #addNewColumnsForAggregation\r
+ */\r
+ private void addAggregateColumns() throws StandardException\r
+ {\r
+ DataDictionary dd = getDataDictionary();\r
+ AggregateNode aggregate = null;\r
+ ColumnReference newColumnRef;\r
+ ResultColumn newRC;\r
+ ResultColumn tmpRC;\r
+ ResultColumn aggInputRC;\r
+ ResultColumnList bottomRCL = childResult.getResultColumns();\r
+ ResultColumnList groupByRCL = resultColumns;\r
+ ResultColumnList aggRCL;\r
+ int aggregatorVColId;\r
+ int aggInputVColId;\r
+ int aggResultVColId;\r
+ \r
+ /*\r
+ ** Now process all of the aggregates. Replace\r
+ ** every aggregate with an RC. We toss out\r
+ ** the list of RCs, we need to get each RC\r
+ ** as we process its corresponding aggregate.\r
+ */\r
+ LanguageFactory lf = getLanguageConnectionContext().getLanguageFactory();\r
+ \r
+ ReplaceAggregatesWithCRVisitor replaceAggsVisitor = \r
+ new ReplaceAggregatesWithCRVisitor(\r
+ (ResultColumnList) getNodeFactory().getNode(\r
+ C_NodeTypes.RESULT_COLUMN_LIST,\r
+ getContextManager()),\r
+ ((FromTable) childResult).getTableNumber(),\r
+ ResultSetNode.class);\r
+ parent.getResultColumns().accept(replaceAggsVisitor);\r
+\r
+ \r
+ if (havingClause != null) \r
+ {\r
+ // replace aggregates in the having clause with column references.\r
+ replaceAggsVisitor = new ReplaceAggregatesWithCRVisitor(\r
+ (ResultColumnList) getNodeFactory().getNode(\r
+ C_NodeTypes.RESULT_COLUMN_LIST,\r
+ getContextManager()), \r
+ ((FromTable)childResult).getTableNumber());\r
+ havingClause.accept(replaceAggsVisitor);\r
+ // make having clause a restriction list in the parent \r
+ // project restrict node.\r
+ ProjectRestrictNode parentPRSN = (ProjectRestrictNode)parent;\r
+ parentPRSN.setRestriction(havingClause);\r
+ }\r
+\r
+ \r
+ /*\r
+ ** For each aggregate\r
+ */\r
+ int alSize = aggregateVector.size();\r
+ for (int index = 0; index < alSize; index++)\r
+ {\r
+ aggregate = (AggregateNode) aggregateVector.elementAt(index);\r
+\r
+ /*\r
+ ** AGG RESULT: Set the aggregate result to null in the\r
+ ** bottom project restrict.\r
+ */\r
+ newRC = (ResultColumn) getNodeFactory().getNode(\r
+ C_NodeTypes.RESULT_COLUMN,\r
+ "##aggregate result",\r
+ aggregate.getNewNullResultExpression(),\r
+ getContextManager());\r
+ newRC.markGenerated();\r
+ newRC.bindResultColumnToExpression();\r
+ bottomRCL.addElement(newRC);\r
+ newRC.setVirtualColumnId(bottomRCL.size());\r
+ aggResultVColId = newRC.getVirtualColumnId();\r
+\r
+ /*\r
+ ** Set the GB aggregrate result column to\r
+ ** point to this. The GB aggregate result\r
+ ** was created when we called\r
+ ** ReplaceAggregatesWithColumnReferencesVisitor()\r
+ */\r
+ newColumnRef = (ColumnReference) getNodeFactory().getNode(\r
+ C_NodeTypes.COLUMN_REFERENCE,\r
+ newRC.getName(),\r
+ null,\r
+ getContextManager());\r
+ newColumnRef.setSource(newRC);\r
+ newColumnRef.setType(newRC.getExpressionType());\r
+ newColumnRef.setNestingLevel(this.getLevel());\r
+ newColumnRef.setSourceLevel(this.getLevel());\r
+ tmpRC = (ResultColumn) getNodeFactory().getNode(\r
+ C_NodeTypes.RESULT_COLUMN,\r
+ newRC.getColumnName(),\r
+ newColumnRef,\r
+ getContextManager());\r
+ tmpRC.markGenerated();\r
+ tmpRC.bindResultColumnToExpression();\r
+ groupByRCL.addElement(tmpRC);\r
+ tmpRC.setVirtualColumnId(groupByRCL.size());\r
+\r
+ /*\r
+ ** Set the column reference to point to\r
+ ** this.\r
+ */\r
+ newColumnRef = aggregate.getGeneratedRef();\r
+ newColumnRef.setSource(tmpRC);\r
+\r
+ /*\r
+ ** AGG INPUT: Create a ResultColumn in the bottom \r
+ ** project restrict that has the expression that is\r
+ ** to be aggregated\r
+ */\r
+ newRC = aggregate.getNewExpressionResultColumn(dd);\r
+ newRC.markGenerated();\r
+ newRC.bindResultColumnToExpression();\r
+ bottomRCL.addElement(newRC);\r
+ newRC.setVirtualColumnId(bottomRCL.size());\r
+ aggInputVColId = newRC.getVirtualColumnId();\r
+ aggInputRC = newRC;\r
+ \r
+ /*\r
+ ** Add a reference to this column into the\r
+ ** group by columns.\r
+ */\r
+ tmpRC = getColumnReference(newRC, dd);\r
+ groupByRCL.addElement(tmpRC);\r
+ tmpRC.setVirtualColumnId(groupByRCL.size());\r
+\r
+ /*\r
+ ** AGGREGATOR: Add a getAggregator method call \r
+ ** to the bottom result column list.\r
+ */\r
+ newRC = aggregate.getNewAggregatorResultColumn(dd);\r
+ newRC.markGenerated();\r
+ newRC.bindResultColumnToExpression();\r
+ bottomRCL.addElement(newRC);\r
+ newRC.setVirtualColumnId(bottomRCL.size());\r
+ aggregatorVColId = newRC.getVirtualColumnId();\r
+\r
+ /*\r
+ ** Add a reference to this column in the Group By result\r
+ ** set.\r
+ */\r
+ tmpRC = getColumnReference(newRC, dd);\r
+ groupByRCL.addElement(tmpRC);\r
+ tmpRC.setVirtualColumnId(groupByRCL.size());\r
+\r
+ /*\r
+ ** Piece together a fake one column rcl that we will use\r
+ ** to generate a proper result description for input\r
+ ** to this agg if it is a user agg.\r
+ */\r
+ aggRCL = (ResultColumnList) getNodeFactory().getNode(\r
+ C_NodeTypes.RESULT_COLUMN_LIST,\r
+ getContextManager());\r
+ aggRCL.addElement(aggInputRC);\r
+\r
+ /*\r
+ ** Note that the column ids in the row are 0 based\r
+ ** so we have to subtract 1.\r
+ */\r
+ aggInfo.addElement(new AggregatorInfo(\r
+ aggregate.getAggregateName(),\r
+ aggregate.getAggregatorClassName(),\r
+ aggInputVColId - 1, // aggregate input column\r
+ aggResultVColId -1, // the aggregate result column\r
+ aggregatorVColId - 1, // the aggregator column \r
+ aggregate.isDistinct(),\r
+ lf.getResultDescription(aggRCL.makeResultDescriptors(), "SELECT")\r
+ ));\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Return the parent node to this one, if there is\r
+ * one. It will return 'this' if there is no generated\r
+ * node above this one.\r
+ *\r
+ * @return the parent node\r
+ */\r
+ public FromTable getParent()\r
+ {\r
+ return parent;\r
+ }\r
+\r
+\r
+ /*\r
+ * Optimizable interface\r
+ */\r
+\r
+ /**\r
+ * @see Optimizable#optimizeIt\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public CostEstimate optimizeIt(\r
+ Optimizer optimizer,\r
+ OptimizablePredicateList predList,\r
+ CostEstimate outerCost,\r
+ RowOrdering rowOrdering)\r
+ throws StandardException\r
+ {\r
+ // RESOLVE: NEED TO FACTOR IN THE COST OF GROUPING (SORTING) HERE\r
+ CostEstimate childCost = ((Optimizable) childResult).optimizeIt(\r
+ optimizer,\r
+ predList,\r
+ outerCost,\r
+ rowOrdering);\r
+\r
+ CostEstimate retval = super.optimizeIt(\r
+ optimizer,\r
+ predList,\r
+ outerCost,\r
+ rowOrdering\r
+ );\r
+\r
+ return retval;\r
+ }\r
+\r
+ /**\r
+ * @see Optimizable#estimateCost\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public CostEstimate estimateCost(OptimizablePredicateList predList,\r
+ ConglomerateDescriptor cd,\r
+ CostEstimate outerCost,\r
+ Optimizer optimizer,\r
+ RowOrdering rowOrdering\r
+ )\r
+ throws StandardException\r
+ {\r
+ // RESOLVE: NEED TO FACTOR IN THE COST OF GROUPING (SORTING) HERE\r
+ //\r
+ CostEstimate childCost = ((Optimizable) childResult).estimateCost(\r
+ predList,\r
+ cd,\r
+ outerCost,\r
+ optimizer,\r
+ rowOrdering);\r
+\r
+ CostEstimate costEstimate = getCostEstimate(optimizer);\r
+ costEstimate.setCost(childCost.getEstimatedCost(),\r
+ childCost.rowCount(),\r
+ childCost.singleScanRowCount());\r
+\r
+ return costEstimate;\r
+ }\r
+\r
+ /**\r
+ * @see org.apache.derby.iapi.sql.compile.Optimizable#pushOptPredicate\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+\r
+ public boolean pushOptPredicate(OptimizablePredicate optimizablePredicate)\r
+ throws StandardException\r
+ {\r
+ return ((Optimizable) childResult).pushOptPredicate(optimizablePredicate);\r
+ }\r
+\r
+ /**\r
+ * Convert this object to a String. See comments in QueryTreeNode.java\r
+ * for how this should be done for tree printing.\r
+ *\r
+ * @return This object as a String\r
+ */\r
+\r
+ public String toString()\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ return "singleInputRowOptimization: " + singleInputRowOptimization + "\n" +\r
+ childResult.toString() + "\n" + super.toString();\r
+ }\r
+ else\r
+ {\r
+ return "";\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Evaluate whether or not the subquery in a FromSubquery is flattenable.\r
+ * Currently, a FSqry is flattenable if all of the following are true:\r
+ * o Subquery is a SelectNode.\r
+ * o It contains no top level subqueries. (RESOLVE - we can relax this)\r
+ * o It does not contain a group by or having clause\r
+ * o It does not contain aggregates.\r
+ *\r
+ * @param fromList The outer from list\r
+ *\r
+ * @return boolean Whether or not the FromSubquery is flattenable.\r
+ */\r
+ public boolean flattenableInFromSubquery(FromList fromList)\r
+ {\r
+ /* Can't flatten a GroupByNode */\r
+ return false;\r
+ }\r
+\r
+ /**\r
+ * Optimize this GroupByNode.\r
+ *\r
+ * @param dataDictionary The DataDictionary to use for optimization\r
+ * @param predicates The PredicateList to optimize. This should\r
+ * be a join predicate.\r
+ * @param outerRows The number of outer joining rows\r
+ *\r
+ * @return ResultSetNode The top of the optimized subtree\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+\r
+ public ResultSetNode optimize(DataDictionary dataDictionary,\r
+ PredicateList predicates,\r
+ double outerRows)\r
+ throws StandardException\r
+ {\r
+ /* We need to implement this method since a PRN can appear above a\r
+ * SelectNode in a query tree.\r
+ */\r
+ childResult = (FromTable) childResult.optimize(\r
+ dataDictionary,\r
+ predicates,\r
+ outerRows);\r
+ Optimizer optimizer = getOptimizer(\r
+ (FromList) getNodeFactory().getNode(\r
+ C_NodeTypes.FROM_LIST,\r
+ getNodeFactory().doJoinOrderOptimization(),\r
+ getContextManager()),\r
+ predicates,\r
+ dataDictionary,\r
+ (RequiredRowOrdering) null);\r
+\r
+ // RESOLVE: NEED TO FACTOR IN COST OF SORTING AND FIGURE OUT HOW\r
+ // MANY ROWS HAVE BEEN ELIMINATED.\r
+ costEstimate = optimizer.newCostEstimate();\r
+\r
+ costEstimate.setCost(childResult.getCostEstimate().getEstimatedCost(),\r
+ childResult.getCostEstimate().rowCount(),\r
+ childResult.getCostEstimate().singleScanRowCount());\r
+\r
+ return this;\r
+ }\r
+\r
+ ResultColumnDescriptor[] makeResultDescriptors()\r
+ {\r
+ return childResult.makeResultDescriptors();\r
+ }\r
+\r
+ /**\r
+ * Return whether or not the underlying ResultSet tree will return\r
+ * a single row, at most.\r
+ * This is important for join nodes where we can save the extra next\r
+ * on the right side if we know that it will return at most 1 row.\r
+ *\r
+ * @return Whether or not the underlying ResultSet tree will return a single row.\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public boolean isOneRowResultSet() throws StandardException\r
+ {\r
+ // Only consider scalar aggregates for now\r
+ return ((groupingList == null) || (groupingList.size() == 0));\r
+ }\r
+\r
+ /**\r
+ * generate the sort result set operating over the source\r
+ * resultset. Adds distinct aggregates to the sort if\r
+ * necessary.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public void generate(ActivationClassBuilder acb,\r
+ MethodBuilder mb)\r
+ throws StandardException\r
+ {\r
+ int orderingItem = 0;\r
+ int aggInfoItem = 0;\r
+ FormatableArrayHolder orderingHolder;\r
+\r
+ /* Get the next ResultSet#, so we can number this ResultSetNode, its\r
+ * ResultColumnList and ResultSet.\r
+ */\r
+ assignResultSetNumber();\r
+\r
+ // Get the final cost estimate from the child.\r
+ costEstimate = childResult.getFinalCostEstimate();\r
+\r
+ /*\r
+ ** Get the column ordering for the sort. Note that\r
+ ** for a scalar aggegate we may not have any ordering\r
+ ** columns (if there are no distinct aggregates).\r
+ ** WARNING: if a distinct aggregate is passed to\r
+ ** SortResultSet it assumes that the last column\r
+ ** is the distinct one. If this assumption changes\r
+ ** then SortResultSet will have to change.\r
+ */\r
+ orderingHolder = acb.getColumnOrdering(groupingList);\r
+ if (addDistinctAggregate)\r
+ {\r
+ orderingHolder = acb.addColumnToOrdering(\r
+ orderingHolder,\r
+ addDistinctAggregateColumnNum);\r
+ }\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (SanityManager.DEBUG_ON("AggregateTrace"))\r
+ {\r
+ StringBuffer s = new StringBuffer();\r
+ \r
+ s.append("Group by column ordering is (");\r
+ ColumnOrdering[] ordering = \r
+ (ColumnOrdering[])orderingHolder.getArray(ColumnOrdering.class);\r
+\r
+ for (int i = 0; i < ordering.length; i++) \r
+ {\r
+ s.append(ordering[i].getColumnId());\r
+ s.append(" ");\r
+ }\r
+ s.append(")");\r
+ SanityManager.DEBUG("AggregateTrace", s.toString());\r
+ }\r
+ }\r
+\r
+ orderingItem = acb.addItem(orderingHolder);\r
+\r
+ /*\r
+ ** We have aggregates, so save the aggInfo\r
+ ** struct in the activation and store the number\r
+ */\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(aggInfo != null,\r
+ "aggInfo not set up as expected");\r
+ }\r
+ aggInfoItem = acb.addItem(aggInfo);\r
+\r
+ acb.pushGetResultSetFactoryExpression(mb);\r
+\r
+ // Generate the child ResultSet\r
+ childResult.generate(acb, mb);\r
+ mb.push(isInSortedOrder);\r
+ mb.push(aggInfoItem);\r
+ mb.push(orderingItem);\r
+\r
+ resultColumns.generateHolder(acb, mb);\r
+\r
+ mb.push(resultColumns.getTotalColumnSize());\r
+ mb.push(resultSetNumber);\r
+\r
+ /* Generate a (Distinct)ScalarAggregateResultSet if scalar aggregates */\r
+ if ((groupingList == null) || (groupingList.size() == 0))\r
+ {\r
+ genScalarAggregateResultSet(acb, mb);\r
+ }\r
+ /* Generate a (Distinct)GroupedAggregateResultSet if grouped aggregates */\r
+ else\r
+ {\r
+ genGroupedAggregateResultSet(acb, mb);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Generate the code to evaluate scalar aggregates.\r
+ *\r
+ */\r
+ private void genScalarAggregateResultSet(ActivationClassBuilder acb,\r
+ MethodBuilder mb)\r
+ {\r
+ /* Generate the (Distinct)ScalarAggregateResultSet:\r
+ * arg1: childExpress - Expression for childResult\r
+ * arg2: isInSortedOrder - true if source result set in sorted order\r
+ * arg3: aggregateItem - entry in saved objects for the aggregates,\r
+ * arg4: orderItem - entry in saved objects for the ordering\r
+ * arg5: Activation\r
+ * arg6: rowAllocator - method to construct rows for fetching\r
+ * from the sort\r
+ * arg7: row size\r
+ * arg8: resultSetNumber\r
+ * arg9: Whether or not to perform min optimization.\r
+ */\r
+ String resultSet = (addDistinctAggregate) ? "getDistinctScalarAggregateResultSet" : "getScalarAggregateResultSet";\r
+\r
+ mb.push(singleInputRowOptimization);\r
+ mb.push(costEstimate.rowCount());\r
+ mb.push(costEstimate.getEstimatedCost());\r
+\r
+ mb.callMethod(VMOpcode.INVOKEINTERFACE, (String) null, resultSet,\r
+ ClassName.NoPutResultSet, 10);\r
+ }\r
+\r
+ /**\r
+ * Generate the code to evaluate grouped aggregates.\r
+ *\r
+ */\r
+ private void genGroupedAggregateResultSet(ActivationClassBuilder acb,\r
+ MethodBuilder mb)\r
+ throws StandardException\r
+ {\r
+ /* Generate the (Distinct)GroupedAggregateResultSet:\r
+ * arg1: childExpress - Expression for childResult\r
+ * arg2: isInSortedOrder - true if source result set in sorted order\r
+ * arg3: aggregateItem - entry in saved objects for the aggregates,\r
+ * arg4: orderItem - entry in saved objects for the ordering\r
+ * arg5: Activation\r
+ * arg6: rowAllocator - method to construct rows for fetching\r
+ * from the sort\r
+ * arg7: row size\r
+ * arg8: resultSetNumber\r
+ */\r
+ String resultSet = (addDistinctAggregate) ? "getDistinctGroupedAggregateResultSet" : "getGroupedAggregateResultSet";\r
+ \r
+ mb.push(costEstimate.rowCount());\r
+ mb.push(costEstimate.getEstimatedCost());\r
+\r
+ mb.callMethod(VMOpcode.INVOKEINTERFACE, (String) null, resultSet,\r
+ ClassName.NoPutResultSet, 9);\r
+\r
+ }\r
+\r
+ ///////////////////////////////////////////////////////////////\r
+ //\r
+ // UTILITIES\r
+ //\r
+ ///////////////////////////////////////////////////////////////\r
+ /**\r
+ * Method for creating a new result column referencing\r
+ * the one passed in.\r
+ *\r
+ * @param targetRC the source\r
+ * @param dd\r
+ *\r
+ * @return the new result column\r
+ *\r
+ * @exception StandardException on error\r
+ */\r
+ private ResultColumn getColumnReference(ResultColumn targetRC, \r
+ DataDictionary dd)\r
+ throws StandardException\r
+ {\r
+ ColumnReference tmpColumnRef;\r
+ ResultColumn newRC;\r
+ \r
+ tmpColumnRef = (ColumnReference) getNodeFactory().getNode(\r
+ C_NodeTypes.COLUMN_REFERENCE,\r
+ targetRC.getName(),\r
+ null,\r
+ getContextManager());\r
+ tmpColumnRef.setSource(targetRC);\r
+ tmpColumnRef.setType(targetRC.getExpressionType());\r
+ tmpColumnRef.setNestingLevel(this.getLevel());\r
+ tmpColumnRef.setSourceLevel(this.getLevel());\r
+ newRC = (ResultColumn) getNodeFactory().getNode(\r
+ C_NodeTypes.RESULT_COLUMN,\r
+ targetRC.getColumnName(),\r
+ tmpColumnRef,\r
+ getContextManager());\r
+ newRC.markGenerated();\r
+ newRC.bindResultColumnToExpression();\r
+ return newRC;\r
+ }\r
+\r
+ /**\r
+ * Consider any optimizations after the optimizer has chosen a plan.\r
+ * Optimizations include:\r
+ * o min optimization for scalar aggregates\r
+ * o max optimization for scalar aggregates\r
+ *\r
+ * @param selectHasPredicates true if SELECT containing this\r
+ * vector/scalar aggregate has a restriction\r
+ *\r
+ * @exception StandardException on error\r
+ */\r
+ void considerPostOptimizeOptimizations(boolean selectHasPredicates)\r
+ throws StandardException\r
+ {\r
+ /* Consider the optimization for min with asc index on that column or\r
+ * max with desc index on that column:\r
+ * o No group by\r
+ * o One of:\r
+ * o min/max(ColumnReference) is only aggregate && source is \r
+ * ordered on the ColumnReference\r
+ * o min/max(ConstantNode)\r
+ * The optimization of the other way around (min with desc index or\r
+ * max with asc index) has the same restrictions with the additional\r
+ * temporary restriction of no qualifications at all (because\r
+ * we don't have true backward scans).\r
+ */\r
+ if (groupingList == null)\r
+ {\r
+ if (aggregateVector.size() == 1)\r
+ {\r
+ AggregateNode an = (AggregateNode) aggregateVector.elementAt(0);\r
+ AggregateDefinition ad = an.getAggregateDefinition();\r
+ if (ad instanceof MaxMinAggregateDefinition)\r
+ {\r
+ if (an.getOperand() instanceof ColumnReference)\r
+ {\r
+ /* See if the underlying ResultSet tree\r
+ * is ordered on the ColumnReference.\r
+ */\r
+ ColumnReference[] crs = new ColumnReference[1];\r
+ crs[0] = (ColumnReference) an.getOperand();\r
+ \r
+ Vector tableVector = new Vector();\r
+ boolean minMaxOptimizationPossible = isOrderedOn(crs, false, tableVector);\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(tableVector.size() <= 1, "bad number of FromBaseTables returned by isOrderedOn() -- "+tableVector.size());\r
+ }\r
+\r
+ if (minMaxOptimizationPossible)\r
+ {\r
+ boolean ascIndex = true;\r
+ int colNum = crs[0].getColumnNumber();\r
+ \r
+ /* Check if we have an access path, this will be\r
+ * null in a join case (See Beetle 4423)\r
+ */\r
+ AccessPath accessPath= getTrulyTheBestAccessPath();\r
+ if (accessPath == null)\r
+ return;\r
+ IndexDescriptor id = accessPath.\r
+ getConglomerateDescriptor().\r
+ getIndexDescriptor();\r
+ int[] keyColumns = id.baseColumnPositions();\r
+ boolean[] isAscending = id.isAscending();\r
+ for (int i = 0; i < keyColumns.length; i++)\r
+ {\r
+ /* in such a query: select min(c3) from\r
+ * tab1 where c1 = 2 and c2 = 5, if prefix keys\r
+ * have equality operator, then we can still use\r
+ * the index. The checking of equality operator\r
+ * has been done in isStrictlyOrderedOn.\r
+ */\r
+ if (colNum == keyColumns[i])\r
+ {\r
+ if (! isAscending[i])\r
+ ascIndex = false;\r
+ break;\r
+ }\r
+ }\r
+ FromBaseTable fbt = (FromBaseTable)tableVector.firstElement();\r
+ MaxMinAggregateDefinition temp = (MaxMinAggregateDefinition)ad;\r
+\r
+ /* MAX ASC NULLABLE \r
+ * ---- ----------\r
+ * TRUE TRUE TRUE/FALSE = Special Last Key Scan (ASC Index Last key with null skips)\r
+ * TRUE FALSE TRUE/FALSE = JustDisableBulk(DESC index 1st key with null skips)\r
+ * FALSE TRUE TRUE/FALSE = JustDisableBulk(ASC index 1st key)\r
+ * FALSE FALSE TRUE/FALSE = Special Last Key Scan(Desc Index Last Key)\r
+ */\r
+\r
+ if (((!temp.isMax()) && ascIndex) || \r
+ ((temp.isMax()) && !ascIndex))\r
+ {\r
+ fbt.disableBulkFetch();\r
+ singleInputRowOptimization = true;\r
+ }\r
+ /*\r
+ ** Max optimization with asc index or min with\r
+ ** desc index is currently more\r
+ ** restrictive than otherwise.\r
+ ** We are getting the store to return the last\r
+ ** row from an index (for the time being, the\r
+ ** store cannot do real backward scans). SO\r
+ ** we cannot do this optimization if we have\r
+ ** any predicates at all.\r
+ */\r
+ else if (!selectHasPredicates && \r
+ ((temp.isMax() && ascIndex) || \r
+ (!temp.isMax() && !ascIndex )))\r
+ {\r
+ fbt.disableBulkFetch();\r
+ fbt.doSpecialMaxScan();\r
+ singleInputRowOptimization = true;\r
+ }\r
+ }\r
+ }\r
+ else if (an.getOperand() instanceof ConstantNode)\r
+ {\r
+ singleInputRowOptimization = true;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Comparator class for GROUP BY expression substitution.\r
+ *\r
+ * This class enables the sorting of a collection of\r
+ * SubstituteExpressionVisitor instances. We sort the visitors\r
+ * during the tree manipulation processing in order to process\r
+ * expressions of higher complexity prior to expressions of\r
+ * lower complexity. Processing the expressions in this order ensures\r
+ * that we choose the best match for an expression, and thus avoids\r
+ * problems where we substitute a sub-expression instead of the\r
+ * full expression. For example, if the statement is:\r
+ * ... GROUP BY a+b, a, a*(a+b), a+b+c\r
+ * we'll process those expressions in the order: a*(a+b),\r
+ * a+b+c, a+b, then a.\r
+ */\r
+ private static final class ExpressionSorter implements Comparator\r
+ {\r
+ public int compare(Object o1, Object o2)\r
+ {\r
+ try {\r
+ ValueNode v1 = ((SubstituteExpressionVisitor)o1).getSource();\r
+ ValueNode v2 = ((SubstituteExpressionVisitor)o2).getSource();\r
+ int refCount1, refCount2;\r
+ CollectNodesVisitor vis = new CollectNodesVisitor(\r
+ ColumnReference.class);\r
+ v1.accept(vis);\r
+ refCount1 = vis.getList().size();\r
+ vis = new CollectNodesVisitor(ColumnReference.class);\r
+ v2.accept(vis);\r
+ refCount2 = vis.getList().size();\r
+ // The ValueNode with the larger number of refs\r
+ // should compare lower. That way we are sorting\r
+ // the expressions in descending order of complexity.\r
+ return refCount2 - refCount1;\r
+ }\r
+ catch (StandardException e)\r
+ {\r
+ throw new RuntimeException(e);\r
+ }\r
+ }\r
+ }\r
+}\r