--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.sql.compile.AggregateNode\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 org.apache.derby.iapi.services.compiler.MethodBuilder;\r
+\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+import org.apache.derby.iapi.services.loader.ClassInspector;\r
+import org.apache.derby.iapi.services.loader.ClassFactory;\r
+\r
+import org.apache.derby.iapi.error.StandardException;\r
+\r
+import org.apache.derby.iapi.sql.dictionary.DataDictionary;\r
+\r
+import org.apache.derby.iapi.sql.compile.CompilerContext;\r
+import org.apache.derby.iapi.sql.compile.C_NodeTypes;\r
+\r
+import org.apache.derby.iapi.types.DataTypeDescriptor;\r
+import org.apache.derby.iapi.types.TypeId;\r
+import org.apache.derby.iapi.reference.SQLState;\r
+\r
+import org.apache.derby.iapi.sql.dictionary.DataDictionary;\r
+import org.apache.derby.iapi.sql.execute.ExecAggregator;\r
+\r
+import org.apache.derby.iapi.error.StandardException;\r
+import org.apache.derby.iapi.reference.SQLState;\r
+\r
+import org.apache.derby.impl.sql.compile.ActivationClassBuilder;\r
+import org.apache.derby.impl.sql.compile.ExpressionClassBuilder;\r
+\r
+import org.apache.derby.catalog.AliasInfo;\r
+import org.apache.derby.catalog.TypeDescriptor;\r
+\r
+import org.apache.derby.impl.sql.compile.CountAggregateDefinition;\r
+import org.apache.derby.impl.sql.compile.MaxMinAggregateDefinition;\r
+import org.apache.derby.impl.sql.compile.SumAvgAggregateDefinition;\r
+\r
+import java.util.Vector;\r
+\r
+/**\r
+ * An Aggregate Node is a node that reprsents a set function/aggregate.\r
+ * It used for all system aggregates as well as user defined aggregates.\r
+ *\r
+ */\r
+\r
+public class AggregateNode extends UnaryOperatorNode\r
+{\r
+ private boolean distinct;\r
+\r
+ private AggregateDefinition uad;\r
+ private StringBuffer aggregatorClassName;\r
+ private String aggregateDefinitionClassName;\r
+ private Class aggregateDefinitionClass;\r
+ private ClassInspector classInspector;\r
+ private String aggregateName;\r
+\r
+ /*\r
+ ** We wind up pushing all aggregates into a different\r
+ ** resultColumnList. When we do this (in \r
+ ** replaceAggregateWithColumnReference), we return a\r
+ ** column reference and create a new result column.\r
+ ** This is used to store that result column.\r
+ */\r
+ private ResultColumn generatedRC;\r
+ private ColumnReference generatedRef;\r
+\r
+ /**\r
+ * Intializer. Used for user defined and internally defined aggregates.\r
+ * Called when binding a StaticMethodNode that we realize is an aggregate.\r
+ *\r
+ * @param operand the value expression for the aggregate\r
+ * @param uadClass the class name for user aggregate definition for the aggregate\r
+ * or the Class for the internal aggregate type.\r
+ * @param distinct boolean indicating whether this is distinct\r
+ * or not.\r
+ * @param aggregateName the name of the aggregate from the user's perspective,\r
+ * e.g. MAX\r
+ *\r
+ * @exception StandardException on error\r
+ */\r
+ public void init\r
+ (\r
+ Object operand,\r
+ Object uadClass,\r
+ Object distinct,\r
+ Object aggregateName\r
+ ) throws StandardException\r
+ {\r
+ super.init(operand);\r
+ this.aggregateName = (String) aggregateName;\r
+\r
+ if (uadClass instanceof String)\r
+ {\r
+ this.aggregateDefinitionClassName = (String) uadClass;\r
+ this.distinct = ((Boolean) distinct).booleanValue();\r
+ }\r
+ else\r
+ {\r
+ this.aggregateDefinitionClass = (Class) uadClass;\r
+ this.aggregateDefinitionClassName =\r
+ aggregateDefinitionClass.getName();\r
+\r
+ // Distinct is meaningless for min and max\r
+ if (!aggregateDefinitionClass.equals(MaxMinAggregateDefinition.class))\r
+ {\r
+ this.distinct = ((Boolean) distinct).booleanValue();\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Replace aggregates in the expression tree with a ColumnReference to\r
+ * that aggregate, append the aggregate to the supplied RCL (assumed to\r
+ * be from the child ResultSetNode) and return the ColumnReference.\r
+ * This is useful for pushing aggregates in the Having clause down to\r
+ * the user's select at parse time. It is also used for moving around \r
+ * Aggregates in the select list when creating the Group By node. In \r
+ * that case it is called <B> after </B> bind time, so we need to create\r
+ * the column differently.\r
+ *\r
+ * @param rcl The RCL to append to.\r
+ * @param tableNumber The tableNumber for the new ColumnReference\r
+ *\r
+ * @return ValueNode The (potentially) modified tree.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public ValueNode replaceAggregatesWithColumnReferences(ResultColumnList rcl, int tableNumber)\r
+ throws StandardException\r
+ {\r
+\r
+ /*\r
+ ** This call is idempotent. Do\r
+ ** the right thing if we have already\r
+ ** replaced ourselves.\r
+ */\r
+ if (generatedRef == null)\r
+ {\r
+ String generatedColName;\r
+ CompilerContext cc = getCompilerContext();\r
+ generatedColName ="SQLCol" + cc.getNextColumnNumber();\r
+ generatedRC = (ResultColumn) getNodeFactory().getNode(\r
+ C_NodeTypes.RESULT_COLUMN,\r
+ generatedColName,\r
+ this,\r
+ getContextManager());\r
+ generatedRC.markGenerated();\r
+ \r
+ /*\r
+ ** Parse time. \r
+ */\r
+ if (getTypeServices() == null)\r
+ {\r
+ generatedRef = (ColumnReference) getNodeFactory().getNode(\r
+ C_NodeTypes.COLUMN_REFERENCE,\r
+ generatedColName,\r
+ null,\r
+ getContextManager());\r
+ }\r
+ else\r
+ {\r
+ generatedRef = (ColumnReference) getNodeFactory().getNode(\r
+ C_NodeTypes.COLUMN_REFERENCE,\r
+ generatedRC.getName(),\r
+ null,\r
+ getContextManager());\r
+ generatedRef.setType(this.getTypeServices());\r
+ }\r
+ // RESOLVE - unknown nesting level, but not correlated, so nesting levels must be 0\r
+ generatedRef.setNestingLevel(0);\r
+ generatedRef.setSourceLevel(0);\r
+ if (tableNumber != -1)\r
+ {\r
+ generatedRef.setTableNumber(tableNumber);\r
+ }\r
+\r
+ rcl.addResultColumn(generatedRC);\r
+\r
+ /* \r
+ ** Mark the ColumnReference as being generated to replace\r
+ ** an aggregate\r
+ */\r
+ generatedRef.markGeneratedToReplaceAggregate();\r
+ }\r
+ else\r
+ {\r
+ rcl.addResultColumn(generatedRC);\r
+ }\r
+\r
+ return generatedRef;\r
+ }\r
+\r
+ /**\r
+ * Get the AggregateDefinition.\r
+ *\r
+ * @return The AggregateDefinition\r
+ */\r
+ AggregateDefinition getAggregateDefinition()\r
+ {\r
+ return uad;\r
+ }\r
+\r
+ /**\r
+ * Get the generated ResultColumn where this\r
+ * aggregate now resides after a call to \r
+ * replaceAggregatesWithColumnReference().\r
+ *\r
+ * @return the result column\r
+ */\r
+ public ResultColumn getGeneratedRC()\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(generatedRC != null, \r
+ "generatedRC is null. replaceAggregateWithColumnReference() "+\r
+ "has not been called on this AggergateNode. Make sure "+\r
+ "the node is under a ResultColumn as expected.");\r
+ }\r
+ \r
+ return generatedRC;\r
+ }\r
+\r
+ /**\r
+ * Get the generated ColumnReference to this\r
+ * aggregate after the parent called\r
+ * replaceAggregatesWithColumnReference().\r
+ *\r
+ * @return the column reference\r
+ */\r
+ public ColumnReference getGeneratedRef()\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(generatedRef != null, \r
+ "generatedRef is null. replaceAggregateWithColumnReference() "+\r
+ "has not been called on this AggergateNode. Make sure "+\r
+ "the node is under a ResultColumn as expected.");\r
+ }\r
+ return generatedRef;\r
+ }\r
+\r
+ /**\r
+ * Bind this operator. Determine the type of the subexpression,\r
+ * and pass that into the UserAggregate.\r
+ *\r
+ * @param fromList The query's FROM list\r
+ * @param subqueryList The subquery list being built as we find SubqueryNodes\r
+ * @param aggregateVector The aggregate list being built as we find AggregateNodes\r
+ *\r
+ * @return The new top of the expression tree.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public ValueNode bindExpression(\r
+ FromList fromList,\r
+ SubqueryList subqueryList,\r
+ Vector aggregateVector)\r
+ throws StandardException\r
+ {\r
+ DataTypeDescriptor dts = null;\r
+ ClassFactory cf;\r
+\r
+ cf = getClassFactory();\r
+ classInspector = cf.getClassInspector();\r
+\r
+ instantiateAggDef();\r
+\r
+ /* Add ourselves to the aggregateVector before we do anything else */\r
+ aggregateVector.addElement(this);\r
+\r
+ // operand being null means a count(*)\r
+ if (operand != null)\r
+ {\r
+ bindOperand(fromList, subqueryList, aggregateVector);\r
+ \r
+ /*\r
+ ** Make sure that we don't have an aggregate \r
+ ** IMMEDIATELY below us. Don't search below\r
+ ** any ResultSetNodes.\r
+ */\r
+ HasNodeVisitor visitor = new HasNodeVisitor(this.getClass(), ResultSetNode.class);\r
+ operand.accept(visitor);\r
+ if (visitor.hasNode())\r
+ {\r
+ throw StandardException.newException(SQLState.LANG_USER_AGGREGATE_CONTAINS_AGGREGATE, \r
+ aggregateName);\r
+ }\r
+\r
+ /*\r
+ ** Check the type of the operand. Make sure that the user\r
+ ** defined aggregate can handle the operand datatype.\r
+ */\r
+ dts = operand.getTypeServices();\r
+\r
+ /* Convert count(nonNullableColumn) to count(*) */\r
+ if (uad instanceof CountAggregateDefinition &&\r
+ !dts.isNullable())\r
+ {\r
+ setOperator(aggregateName);\r
+ setMethodName(aggregateName);\r
+ }\r
+\r
+ /*\r
+ ** If we have a distinct, then the value expression\r
+ ** MUST implement Orderable because we are going\r
+ ** to process it using it as part of a sort.\r
+ */\r
+ if (distinct)\r
+ {\r
+ /*\r
+ ** For now, we check to see if orderable() returns\r
+ ** true for this type. In the future we may need\r
+ ** to check to see if the type implements Orderable\r
+ **\r
+ */\r
+ if (!operand.getTypeId().orderable(cf))\r
+ {\r
+ throw StandardException.newException(SQLState.LANG_COLUMN_NOT_ORDERABLE_DURING_EXECUTION, \r
+ dts.getTypeId().getSQLTypeName());\r
+ }\r
+\r
+ }\r
+\r
+ /*\r
+ ** Don't allow an untyped null\r
+ */\r
+ if (operand instanceof UntypedNullConstantNode)\r
+ {\r
+ throw StandardException.newException(SQLState.LANG_USER_AGGREGATE_BAD_TYPE_NULL, aggregateName);\r
+ }\r
+ }\r
+\r
+ /*\r
+ ** Ask the aggregate definition whether it can handle\r
+ ** the input datatype.\r
+ */\r
+ aggregatorClassName = new StringBuffer();\r
+ DataTypeDescriptor resultType = uad.getAggregator(dts, aggregatorClassName);\r
+\r
+ if (resultType == null)\r
+ {\r
+ throw StandardException.newException(SQLState.LANG_USER_AGGREGATE_BAD_TYPE, \r
+ aggregateName, \r
+ operand.getTypeId().getSQLTypeName());\r
+ }\r
+\r
+ checkAggregatorClassName(aggregatorClassName.toString());\r
+\r
+ setType(resultType);\r
+\r
+ return this;\r
+ }\r
+\r
+ /*\r
+ ** Make sure the aggregator class is ok\r
+ */\r
+ private void checkAggregatorClassName(String className) throws StandardException\r
+ {\r
+ verifyClassExist(className);\r
+\r
+ if (!classInspector.assignableTo(className, "org.apache.derby.iapi.sql.execute.ExecAggregator"))\r
+ {\r
+ throw StandardException.newException(SQLState.LANG_BAD_AGGREGATOR_CLASS2, \r
+ className, \r
+ aggregateName,\r
+ operand.getTypeId().getSQLTypeName());\r
+ }\r
+ }\r
+\r
+ \r
+ /*\r
+ ** Instantiate the aggregate definition.\r
+ */\r
+ private void instantiateAggDef() throws StandardException\r
+ {\r
+ Class theClass = aggregateDefinitionClass;\r
+\r
+ // get the class\r
+ if (theClass == null)\r
+ {\r
+ String aggClassName = aggregateDefinitionClassName;\r
+ verifyClassExist(aggClassName);\r
+\r
+ try\r
+ {\r
+ theClass = classInspector.getClass(aggClassName);\r
+ }\r
+ catch (Throwable t)\r
+ {\r
+ throw StandardException.unexpectedUserException(t);\r
+ }\r
+ }\r
+\r
+ // get an instance\r
+ Object instance = null;\r
+ try\r
+ {\r
+ instance = theClass.newInstance();\r
+ //Added by Jeff Huang\r
+ //TODO: FIXIT\r
+ }\r
+ catch (Throwable t)\r
+ {\r
+ throw StandardException.unexpectedUserException(t);\r
+ }\r
+\r
+ if (!(instance instanceof AggregateDefinition))\r
+ {\r
+ throw StandardException.newException(SQLState.LANG_INVALID_USER_AGGREGATE_DEFINITION2, aggregateDefinitionClassName);\r
+ }\r
+\r
+ if (instance instanceof MaxMinAggregateDefinition)\r
+ {\r
+ MaxMinAggregateDefinition temp = (MaxMinAggregateDefinition)instance;\r
+ if (aggregateName.equals("MAX"))\r
+ temp.setMaxOrMin(true);\r
+ else\r
+ temp.setMaxOrMin(false);\r
+ }\r
+\r
+ if (instance instanceof SumAvgAggregateDefinition)\r
+ {\r
+ SumAvgAggregateDefinition temp1 = (SumAvgAggregateDefinition)instance;\r
+ if (aggregateName.equals("SUM"))\r
+ temp1.setSumOrAvg(true);\r
+ else\r
+ temp1.setSumOrAvg(false);\r
+ }\r
+\r
+ this.uad = (AggregateDefinition)instance;\r
+ \r
+ setOperator(aggregateName);\r
+ setMethodName(aggregateDefinitionClassName);\r
+\r
+ }\r
+\r
+ /**\r
+ * Indicate whether this aggregate is distinct or not.\r
+ *\r
+ * @return true/false\r
+ */\r
+ public boolean isDistinct()\r
+ {\r
+ return distinct;\r
+ }\r
+\r
+ /**\r
+ * Get the class that implements that aggregator for this\r
+ * node.\r
+ *\r
+ * @return the class name\r
+ */\r
+ public String getAggregatorClassName()\r
+ {\r
+ return aggregatorClassName.toString();\r
+ }\r
+\r
+ /**\r
+ * Get the class that implements that aggregator for this\r
+ * node.\r
+ *\r
+ * @return the class name\r
+ */\r
+ public String getAggregateName()\r
+ {\r
+ return aggregateName;\r
+ }\r
+\r
+ /**\r
+ * Get the result column that has a new aggregator.\r
+ * This aggregator will be fed into the sorter.\r
+ *\r
+ * @param dd the data dictionary\r
+ *\r
+ * @return the result column. WARNING: it still needs to be bound\r
+ *\r
+ * @exception StandardException on error\r
+ */\r
+ public ResultColumn getNewAggregatorResultColumn(DataDictionary dd)\r
+ throws StandardException\r
+ {\r
+ String className = aggregatorClassName.toString();\r
+\r
+ TypeId compTypeId = TypeId.getSQLTypeForJavaType(className);\r
+\r
+ /*\r
+ ** Create a null of the right type. The proper aggregators\r
+ ** are created dynamically by the SortObservers\r
+ */\r
+ ConstantNode nullNode = getNullNode(\r
+ compTypeId,\r
+ getContextManager(),\r
+ getTypeServices().getCollationType(),\r
+ getTypeServices().getCollationDerivation()\r
+ ); // no params\r
+\r
+ nullNode.bindExpression(\r
+ null, // from\r
+ null, // subquery\r
+ null); // aggregate\r
+\r
+ /*\r
+ ** Create a result column with this new node below\r
+ ** it.\r
+ */\r
+ return (ResultColumn) getNodeFactory().getNode(\r
+ C_NodeTypes.RESULT_COLUMN,\r
+ aggregateName,\r
+ nullNode, \r
+ getContextManager());\r
+ }\r
+\r
+\r
+ /**\r
+ * Get the aggregate expression in a new result\r
+ * column.\r
+ *\r
+ * @param dd the data dictionary\r
+ *\r
+ * @return the result column. WARNING: it still needs to be bound\r
+ *\r
+ * @exception StandardException on error\r
+ */\r
+ public ResultColumn getNewExpressionResultColumn(DataDictionary dd)\r
+ throws StandardException\r
+ {\r
+ ValueNode node;\r
+ /*\r
+ ** Create a result column with the aggrergate operand\r
+ ** it. If there is no operand, then we have a COUNT(*),\r
+ ** so we'll have to create a new null node and put\r
+ ** that in place.\r
+ */\r
+ node = (operand == null) ?\r
+ this.getNewNullResultExpression() :\r
+ operand;\r
+\r
+ return (ResultColumn) getNodeFactory().getNode(\r
+ C_NodeTypes.RESULT_COLUMN,\r
+ "##aggregate expression",\r
+ node,\r
+ getContextManager());\r
+ }\r
+\r
+ /**\r
+ * Get the null aggregate result expression\r
+ * column.\r
+ *\r
+ * @return the value node\r
+ *\r
+ * @exception StandardException on error\r
+ */\r
+ public ValueNode getNewNullResultExpression()\r
+ throws StandardException\r
+ {\r
+ /*\r
+ ** Create a result column with the aggrergate operand\r
+ ** it.\r
+ */\r
+ return getNullNode(this.getTypeId(),\r
+ getContextManager(), this.getTypeServices().getCollationType(),\r
+ this.getTypeServices().getCollationDerivation());\r
+ }\r
+\r
+ /**\r
+ * Do code generation for this unary operator. Should\r
+ * never be called for an aggregate -- it should be converted\r
+ * into something else by code generation time.\r
+ *\r
+ * @param acb The ExpressionClassBuilder for the class we're generating\r
+ * @param mb The method the code to place the code\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public void generateExpression(ExpressionClassBuilder acb,\r
+ MethodBuilder mb)\r
+ throws StandardException\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.THROWASSERT("generateExpression() should never "+\r
+ "be called on an AggregateNode. "+\r
+ "replaceAggregatesWithColumnReferences should have " +\r
+ "been called prior to generateExpression");\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Print a string ref of this node.\r
+ *\r
+ * @return a string representation of this node \r
+ */\r
+ public String toString()\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ return "Aggregate: "+aggregateName+\r
+ "\ndistinct: "+distinct+\r
+ super.toString();\r
+ }\r
+ else\r
+ {\r
+ return "";\r
+ }\r
+ }\r
+\r
+ public boolean isConstant()\r
+ {\r
+ return false;\r
+ }\r
+ \r
+ public boolean constantExpression(PredicateList where)\r
+ {\r
+ return false;\r
+ }\r
+}\r