001: /*
002:
003: Derby - Class org.apache.derby.impl.sql.compile.AggregateNode
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to you under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: the License. You may obtain a copy of the License at
011:
012: http://www.apache.org/licenses/LICENSE-2.0
013:
014: Unless required by applicable law or agreed to in writing, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derby.impl.sql.compile;
023:
024: import org.apache.derby.iapi.services.compiler.MethodBuilder;
025:
026: import org.apache.derby.iapi.services.sanity.SanityManager;
027: import org.apache.derby.iapi.services.loader.ClassInspector;
028: import org.apache.derby.iapi.services.loader.ClassFactory;
029:
030: import org.apache.derby.iapi.error.StandardException;
031:
032: import org.apache.derby.iapi.sql.dictionary.DataDictionary;
033:
034: import org.apache.derby.iapi.sql.compile.CompilerContext;
035: import org.apache.derby.iapi.sql.compile.C_NodeTypes;
036:
037: import org.apache.derby.iapi.types.DataTypeDescriptor;
038: import org.apache.derby.iapi.types.TypeId;
039: import org.apache.derby.iapi.reference.SQLState;
040:
041: import org.apache.derby.iapi.sql.dictionary.DataDictionary;
042: import org.apache.derby.iapi.sql.execute.ExecAggregator;
043:
044: import org.apache.derby.iapi.error.StandardException;
045: import org.apache.derby.iapi.reference.SQLState;
046:
047: import org.apache.derby.impl.sql.compile.ActivationClassBuilder;
048: import org.apache.derby.impl.sql.compile.ExpressionClassBuilder;
049:
050: import org.apache.derby.catalog.AliasInfo;
051: import org.apache.derby.catalog.TypeDescriptor;
052:
053: import org.apache.derby.impl.sql.compile.CountAggregateDefinition;
054: import org.apache.derby.impl.sql.compile.MaxMinAggregateDefinition;
055: import org.apache.derby.impl.sql.compile.SumAvgAggregateDefinition;
056:
057: import java.util.Vector;
058:
059: /**
060: * An Aggregate Node is a node that reprsents a set function/aggregate.
061: * It used for all system aggregates as well as user defined aggregates.
062: *
063: * @author jamie
064: */
065:
066: public class AggregateNode extends UnaryOperatorNode {
067: private boolean distinct;
068:
069: private AggregateDefinition uad;
070: private StringBuffer aggregatorClassName;
071: private String aggregateDefinitionClassName;
072: private Class aggregateDefinitionClass;
073: private ClassInspector classInspector;
074: private String aggregateName;
075:
076: /*
077: ** We wind up pushing all aggregates into a different
078: ** resultColumnList. When we do this (in
079: ** replaceAggregateWithColumnReference), we return a
080: ** column reference and create a new result column.
081: ** This is used to store that result column.
082: */
083: private ResultColumn generatedRC;
084: private ColumnReference generatedRef;
085:
086: /**
087: * Intializer. Used for user defined and internally defined aggregates.
088: * Called when binding a StaticMethodNode that we realize is an aggregate.
089: *
090: * @param operand the value expression for the aggregate
091: * @param uadClass the class name for user aggregate definition for the aggregate
092: * or the Class for the internal aggregate type.
093: * @param distinct boolean indicating whether this is distinct
094: * or not.
095: * @param aggregateName the name of the aggregate from the user's perspective,
096: * e.g. MAX
097: *
098: * @exception StandardException on error
099: */
100: public void init(Object operand, Object uadClass, Object distinct,
101: Object aggregateName) throws StandardException {
102: super .init(operand);
103: this .aggregateName = (String) aggregateName;
104:
105: if (uadClass instanceof String) {
106: this .aggregateDefinitionClassName = (String) uadClass;
107: this .distinct = ((Boolean) distinct).booleanValue();
108: } else {
109: this .aggregateDefinitionClass = (Class) uadClass;
110: this .aggregateDefinitionClassName = aggregateDefinitionClass
111: .getName();
112:
113: // Distinct is meaningless for min and max
114: if (!aggregateDefinitionClass
115: .equals(MaxMinAggregateDefinition.class)) {
116: this .distinct = ((Boolean) distinct).booleanValue();
117: }
118: }
119: }
120:
121: /**
122: * Replace aggregates in the expression tree with a ColumnReference to
123: * that aggregate, append the aggregate to the supplied RCL (assumed to
124: * be from the child ResultSetNode) and return the ColumnReference.
125: * This is useful for pushing aggregates in the Having clause down to
126: * the user's select at parse time. It is also used for moving around
127: * Aggregates in the select list when creating the Group By node. In
128: * that case it is called <B> after </B> bind time, so we need to create
129: * the column differently.
130: *
131: * @param rcl The RCL to append to.
132: * @param tableNumber The tableNumber for the new ColumnReference
133: *
134: * @return ValueNode The (potentially) modified tree.
135: *
136: * @exception StandardException Thrown on error
137: */
138: public ValueNode replaceAggregatesWithColumnReferences(
139: ResultColumnList rcl, int tableNumber)
140: throws StandardException {
141:
142: /*
143: ** This call is idempotent. Do
144: ** the right thing if we have already
145: ** replaced ourselves.
146: */
147: if (generatedRef == null) {
148: String generatedColName;
149: CompilerContext cc = getCompilerContext();
150: generatedColName = "SQLCol" + cc.getNextColumnNumber();
151: generatedRC = (ResultColumn) getNodeFactory().getNode(
152: C_NodeTypes.RESULT_COLUMN, generatedColName, this ,
153: getContextManager());
154: generatedRC.markGenerated();
155:
156: /*
157: ** Parse time.
158: */
159: if (getTypeServices() == null) {
160: generatedRef = (ColumnReference) getNodeFactory()
161: .getNode(C_NodeTypes.COLUMN_REFERENCE,
162: generatedColName, null,
163: getContextManager());
164: } else {
165: generatedRef = (ColumnReference) getNodeFactory()
166: .getNode(C_NodeTypes.COLUMN_REFERENCE,
167: generatedRC.getName(), null,
168: getContextManager());
169: generatedRef.setType(this .getTypeServices());
170: }
171: // RESOLVE - unknown nesting level, but not correlated, so nesting levels must be 0
172: generatedRef.setNestingLevel(0);
173: generatedRef.setSourceLevel(0);
174: if (tableNumber != -1) {
175: generatedRef.setTableNumber(tableNumber);
176: }
177:
178: rcl.addResultColumn(generatedRC);
179:
180: /*
181: ** Mark the ColumnReference as being generated to replace
182: ** an aggregate
183: */
184: generatedRef.markGeneratedToReplaceAggregate();
185: } else {
186: rcl.addResultColumn(generatedRC);
187: }
188:
189: return generatedRef;
190: }
191:
192: /**
193: * Get the AggregateDefinition.
194: *
195: * @return The AggregateDefinition
196: */
197: AggregateDefinition getAggregateDefinition() {
198: return uad;
199: }
200:
201: /**
202: * Get the generated ResultColumn where this
203: * aggregate now resides after a call to
204: * replaceAggregatesWithColumnReference().
205: *
206: * @return the result column
207: */
208: public ResultColumn getGeneratedRC() {
209: if (SanityManager.DEBUG) {
210: SanityManager
211: .ASSERT(
212: generatedRC != null,
213: "generatedRC is null. replaceAggregateWithColumnReference() "
214: + "has not been called on this AggergateNode. Make sure "
215: + "the node is under a ResultColumn as expected.");
216: }
217:
218: return generatedRC;
219: }
220:
221: /**
222: * Get the generated ColumnReference to this
223: * aggregate after the parent called
224: * replaceAggregatesWithColumnReference().
225: *
226: * @return the column reference
227: */
228: public ColumnReference getGeneratedRef() {
229: if (SanityManager.DEBUG) {
230: SanityManager
231: .ASSERT(
232: generatedRef != null,
233: "generatedRef is null. replaceAggregateWithColumnReference() "
234: + "has not been called on this AggergateNode. Make sure "
235: + "the node is under a ResultColumn as expected.");
236: }
237: return generatedRef;
238: }
239:
240: /**
241: * Bind this operator. Determine the type of the subexpression,
242: * and pass that into the UserAggregate.
243: *
244: * @param fromList The query's FROM list
245: * @param subqueryList The subquery list being built as we find SubqueryNodes
246: * @param aggregateVector The aggregate list being built as we find AggregateNodes
247: *
248: * @return The new top of the expression tree.
249: *
250: * @exception StandardException Thrown on error
251: */
252: public ValueNode bindExpression(FromList fromList,
253: SubqueryList subqueryList, Vector aggregateVector)
254: throws StandardException {
255: TypeId outType;
256: TypeId inputType = null;
257: Class inputClass = null;
258: String inputTypeName = null;
259: Class inputInterfaceClass = null;
260: String inputInterfaceName = null;
261: DataTypeDescriptor dts = null;
262: TypeDescriptor resultType = null;
263: ClassFactory cf;
264:
265: cf = getClassFactory();
266: classInspector = cf.getClassInspector();
267:
268: instantiateAggDef();
269:
270: /* Add ourselves to the aggregateVector before we do anything else */
271: aggregateVector.addElement(this );
272:
273: super .bindExpression(fromList, subqueryList, aggregateVector);
274:
275: if (operand != null) {
276: /*
277: ** Make sure that we don't have an aggregate
278: ** IMMEDIATELY below us. Don't search below
279: ** any ResultSetNodes.
280: */
281: HasNodeVisitor visitor = new HasNodeVisitor(
282: this .getClass(), ResultSetNode.class);
283: operand.accept(visitor);
284: if (visitor.hasNode()) {
285: throw StandardException
286: .newException(
287: SQLState.LANG_USER_AGGREGATE_CONTAINS_AGGREGATE,
288: aggregateName);
289: }
290:
291: /*
292: ** Check the type of the operand. Make sure that the user
293: ** defined aggregate can handle the operand datatype.
294: */
295: dts = operand.getTypeServices();
296:
297: /* Convert count(nonNullableColumn) to count(*) */
298: if (uad instanceof CountAggregateDefinition
299: && !dts.isNullable()) {
300: setOperator(aggregateName);
301: setMethodName(aggregateName);
302: }
303:
304: /*
305: ** If we have a distinct, then the value expression
306: ** MUST implement Orderable because we are going
307: ** to process it using it as part of a sort.
308: */
309: if (distinct) {
310: /*
311: ** For now, we check to see if orderable() returns
312: ** true for this type. In the future we may need
313: ** to check to see if the type implements Orderable
314: **
315: */
316: if (!operand.getTypeId().orderable(cf)) {
317: throw StandardException
318: .newException(
319: SQLState.LANG_COLUMN_NOT_ORDERABLE_DURING_EXECUTION,
320: dts.getTypeId().getSQLTypeName());
321: }
322:
323: }
324:
325: /*
326: ** Don't allow an untyped null
327: */
328: if (operand instanceof UntypedNullConstantNode) {
329: throw StandardException.newException(
330: SQLState.LANG_USER_AGGREGATE_BAD_TYPE_NULL,
331: aggregateName);
332: }
333: }
334:
335: /*
336: ** Ask the aggregate definition whether it can handle
337: ** the input datatype. If an exception is thrown,
338: ** barf.
339: */
340: try {
341: aggregatorClassName = new StringBuffer();
342: resultType = uad.getAggregator(dts, aggregatorClassName);
343: } catch (Exception e) {
344: //RESOLVE: would be a good idea to add some additional text to
345: // this error, like during getResultDataType on aggregate x
346: // maybe enhance this error everywhere (seems like execution
347: // should also add some text, at the very least saying during
348: // execution. see Compiltion/Generator/UserExpressionBuilder.java
349: throw StandardException.unexpectedUserException(e);
350: }
351:
352: if (resultType == null) {
353: throw StandardException
354: .newException(
355: SQLState.LANG_USER_AGGREGATE_BAD_TYPE,
356: aggregateName, operand.getTypeId()
357: .getSQLTypeName());
358: }
359:
360: checkAggregatorClassName(aggregatorClassName.toString());
361:
362: /*
363: ** Try for a built in type matching the
364: ** type name.
365: */
366: TypeId compTypeId = TypeId.getBuiltInTypeId(resultType
367: .getTypeName());
368: /*
369: ** If not built in, it is probably a java type.
370: ** Get the sql type descriptor for that.
371: */
372: if (compTypeId == null) {
373: compTypeId = TypeId.getSQLTypeForJavaType(resultType
374: .getTypeName());
375: }
376:
377: /*
378: ** Now set the type. Get a new descriptor
379: ** in case the user returned the same descriptor
380: ** as was passed in.
381: */
382: setType(new DataTypeDescriptor(compTypeId, resultType
383: .getPrecision(), resultType.getScale(), resultType
384: .isNullable(), resultType.getMaximumWidth()));
385:
386: return this ;
387: }
388:
389: /*
390: ** Make sure the aggregator class is ok
391: */
392: private void checkAggregatorClassName(String className)
393: throws StandardException {
394: className = verifyClassExist(className, false);
395:
396: if (!classInspector.assignableTo(className,
397: "org.apache.derby.iapi.sql.execute.ExecAggregator")) {
398: throw StandardException
399: .newException(SQLState.LANG_BAD_AGGREGATOR_CLASS2,
400: className, aggregateName, operand
401: .getTypeId().getSQLTypeName());
402: }
403: }
404:
405: /*
406: ** Instantiate the aggregate definition.
407: */
408: private void instantiateAggDef() throws StandardException {
409: Class theClass = aggregateDefinitionClass;
410:
411: // get the class
412: if (theClass == null) {
413: String aggClassName = aggregateDefinitionClassName;
414: aggClassName = verifyClassExist(aggClassName, false);
415:
416: try {
417: theClass = classInspector.getClass(aggClassName);
418: } catch (Throwable t) {
419: throw StandardException.unexpectedUserException(t);
420: }
421: }
422:
423: // get an instance
424: Object instance = null;
425: try {
426: instance = theClass.newInstance();
427: } catch (Throwable t) {
428: throw StandardException.unexpectedUserException(t);
429: }
430:
431: if (!(instance instanceof AggregateDefinition)) {
432: throw StandardException.newException(
433: SQLState.LANG_INVALID_USER_AGGREGATE_DEFINITION2,
434: aggregateDefinitionClassName);
435: }
436:
437: if (instance instanceof MaxMinAggregateDefinition) {
438: MaxMinAggregateDefinition temp = (MaxMinAggregateDefinition) instance;
439: if (aggregateName.equals("MAX"))
440: temp.setMaxOrMin(true);
441: else
442: temp.setMaxOrMin(false);
443: }
444:
445: if (instance instanceof SumAvgAggregateDefinition) {
446: SumAvgAggregateDefinition temp1 = (SumAvgAggregateDefinition) instance;
447: if (aggregateName.equals("SUM"))
448: temp1.setSumOrAvg(true);
449: else
450: temp1.setSumOrAvg(false);
451: }
452:
453: this .uad = (AggregateDefinition) instance;
454:
455: setOperator(aggregateName);
456: setMethodName(aggregateDefinitionClassName);
457:
458: }
459:
460: /**
461: * Indicate whether this aggregate is distinct or not.
462: *
463: * @return true/false
464: */
465: public boolean isDistinct() {
466: return distinct;
467: }
468:
469: /**
470: * Get the class that implements that aggregator for this
471: * node.
472: *
473: * @return the class name
474: */
475: public String getAggregatorClassName() {
476: return aggregatorClassName.toString();
477: }
478:
479: /**
480: * Get the class that implements that aggregator for this
481: * node.
482: *
483: * @return the class name
484: */
485: public String getAggregateName() {
486: return aggregateName;
487: }
488:
489: /**
490: * Get the result column that has a new aggregator.
491: * This aggregator will be fed into the sorter.
492: *
493: * @param dd the data dictionary
494: *
495: * @return the result column. WARNING: it still needs to be bound
496: *
497: * @exception StandardException on error
498: */
499: public ResultColumn getNewAggregatorResultColumn(DataDictionary dd)
500: throws StandardException {
501: String className = aggregatorClassName.toString();
502:
503: TypeId compTypeId = TypeId.getSQLTypeForJavaType(className);
504:
505: /*
506: ** Create a null of the right type. The proper aggregators
507: ** are created dynamically by the SortObservers
508: */
509: ConstantNode nullNode = getNullNode(compTypeId,
510: getContextManager()); // no params
511:
512: nullNode.bindExpression(null, // from
513: null, // subquery
514: null); // aggregate
515:
516: /*
517: ** Create a result column with this new node below
518: ** it.
519: */
520: return (ResultColumn) getNodeFactory().getNode(
521: C_NodeTypes.RESULT_COLUMN, aggregateName, nullNode,
522: getContextManager());
523: }
524:
525: /**
526: * Get the aggregate expression in a new result
527: * column.
528: *
529: * @param dd the data dictionary
530: *
531: * @return the result column. WARNING: it still needs to be bound
532: *
533: * @exception StandardException on error
534: */
535: public ResultColumn getNewExpressionResultColumn(DataDictionary dd)
536: throws StandardException {
537: ValueNode node;
538: /*
539: ** Create a result column with the aggrergate operand
540: ** it. If there is no operand, then we have a COUNT(*),
541: ** so we'll have to create a new null node and put
542: ** that in place.
543: */
544: node = (operand == null) ? this .getNewNullResultExpression()
545: : operand;
546:
547: return (ResultColumn) getNodeFactory().getNode(
548: C_NodeTypes.RESULT_COLUMN, "##aggregate expression",
549: node, getContextManager());
550: }
551:
552: /**
553: * Get the null aggregate result expression
554: * column.
555: *
556: * @return the value node
557: *
558: * @exception StandardException on error
559: */
560: public ValueNode getNewNullResultExpression()
561: throws StandardException {
562: /*
563: ** Create a result column with the aggrergate operand
564: ** it.
565: */
566: return getNullNode(this .getTypeId(), getContextManager());
567: }
568:
569: /**
570: * Do code generation for this unary operator. Should
571: * never be called for an aggregate -- it should be converted
572: * into something else by code generation time.
573: *
574: * @param acb The ExpressionClassBuilder for the class we're generating
575: * @param mb The method the code to place the code
576: *
577: * @exception StandardException Thrown on error
578: */
579: public void generateExpression(ExpressionClassBuilder acb,
580: MethodBuilder mb) throws StandardException {
581: if (SanityManager.DEBUG) {
582: SanityManager
583: .THROWASSERT("generateExpression() should never "
584: + "be called on an AggregateNode. "
585: + "replaceAggregatesWithColumnReferences should have "
586: + "been called prior to generateExpression");
587: }
588: }
589:
590: /**
591: * Print a string ref of this node.
592: *
593: * @return a string representation of this node
594: */
595: public String toString() {
596: if (SanityManager.DEBUG) {
597: return "Aggregate: " + aggregateName + "\ndistinct: "
598: + distinct + super .toString();
599: } else {
600: return "";
601: }
602: }
603: }
|