KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > derby > impl > sql > compile > AggregateNode


1 /*
2
3    Derby - Class org.apache.derby.impl.sql.compile.AggregateNode
4
5    Licensed to the Apache Software Foundation (ASF) under one or more
6    contributor license agreements. See the NOTICE file distributed with
7    this work for additional information regarding copyright ownership.
8    The ASF licenses this file to you under the Apache License, Version 2.0
9    (the "License"); you may not use this file except in compliance with
10    the License. You may obtain a copy of the License at
11
12       http://www.apache.org/licenses/LICENSE-2.0
13
14    Unless required by applicable law or agreed to in writing, software
15    distributed under the License is distributed on an "AS IS" BASIS,
16    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17    See the License for the specific language governing permissions and
18    limitations under the License.
19
20  */

21
22 package org.apache.derby.impl.sql.compile;
23
24 import org.apache.derby.iapi.services.compiler.MethodBuilder;
25
26 import org.apache.derby.iapi.services.sanity.SanityManager;
27 import org.apache.derby.iapi.services.loader.ClassInspector;
28 import org.apache.derby.iapi.services.loader.ClassFactory;
29
30 import org.apache.derby.iapi.error.StandardException;
31
32 import org.apache.derby.iapi.sql.dictionary.DataDictionary;
33
34 import org.apache.derby.iapi.sql.compile.CompilerContext;
35 import org.apache.derby.iapi.sql.compile.C_NodeTypes;
36
37 import org.apache.derby.iapi.types.DataTypeDescriptor;
38 import org.apache.derby.iapi.types.TypeId;
39 import org.apache.derby.iapi.reference.SQLState;
40
41 import org.apache.derby.iapi.sql.dictionary.DataDictionary;
42 import org.apache.derby.iapi.sql.execute.ExecAggregator;
43
44 import org.apache.derby.iapi.error.StandardException;
45 import org.apache.derby.iapi.reference.SQLState;
46
47 import org.apache.derby.impl.sql.compile.ActivationClassBuilder;
48 import org.apache.derby.impl.sql.compile.ExpressionClassBuilder;
49
50 import org.apache.derby.catalog.AliasInfo;
51 import org.apache.derby.catalog.TypeDescriptor;
52
53 import org.apache.derby.impl.sql.compile.CountAggregateDefinition;
54 import org.apache.derby.impl.sql.compile.MaxMinAggregateDefinition;
55 import org.apache.derby.impl.sql.compile.SumAvgAggregateDefinition;
56
57 import java.util.Vector JavaDoc;
58
59 /**
60  * An Aggregate Node is a node that reprsents a set function/aggregate.
61  * It used for all system aggregates as well as user defined aggregates.
62  *
63  * @author jamie
64  */

65
66 public class AggregateNode extends UnaryOperatorNode
67 {
68     private boolean distinct;
69
70     private AggregateDefinition uad;
71     private StringBuffer JavaDoc aggregatorClassName;
72     private String JavaDoc aggregateDefinitionClassName;
73     private Class JavaDoc aggregateDefinitionClass;
74     private ClassInspector classInspector;
75     private String JavaDoc aggregateName;
76
77     /*
78     ** We wind up pushing all aggregates into a different
79     ** resultColumnList. When we do this (in
80     ** replaceAggregateWithColumnReference), we return a
81     ** column reference and create a new result column.
82     ** This is used to store that result column.
83     */

84     private ResultColumn generatedRC;
85     private ColumnReference generatedRef;
86
87     /**
88      * Intializer. Used for user defined and internally defined aggregates.
89      * Called when binding a StaticMethodNode that we realize is an aggregate.
90      *
91      * @param operand the value expression for the aggregate
92      * @param uadClass the class name for user aggregate definition for the aggregate
93      * or the Class for the internal aggregate type.
94      * @param distinct boolean indicating whether this is distinct
95      * or not.
96      * @param aggregateName the name of the aggregate from the user's perspective,
97      * e.g. MAX
98      *
99      * @exception StandardException on error
100      */

101     public void init
102     (
103         Object JavaDoc operand,
104         Object JavaDoc uadClass,
105         Object JavaDoc distinct,
106         Object JavaDoc aggregateName
107     ) throws StandardException
108     {
109         super.init(operand);
110         this.aggregateName = (String JavaDoc) aggregateName;
111
112         if (uadClass instanceof String JavaDoc)
113         {
114             this.aggregateDefinitionClassName = (String JavaDoc) uadClass;
115             this.distinct = ((Boolean JavaDoc) distinct).booleanValue();
116         }
117         else
118         {
119             this.aggregateDefinitionClass = (Class JavaDoc) uadClass;
120             this.aggregateDefinitionClassName =
121                                         aggregateDefinitionClass.getName();
122
123             // Distinct is meaningless for min and max
124
if (!aggregateDefinitionClass.equals(MaxMinAggregateDefinition.class))
125             {
126                 this.distinct = ((Boolean JavaDoc) distinct).booleanValue();
127             }
128         }
129     }
130
131     /**
132      * Replace aggregates in the expression tree with a ColumnReference to
133      * that aggregate, append the aggregate to the supplied RCL (assumed to
134      * be from the child ResultSetNode) and return the ColumnReference.
135      * This is useful for pushing aggregates in the Having clause down to
136      * the user's select at parse time. It is also used for moving around
137      * Aggregates in the select list when creating the Group By node. In
138      * that case it is called <B> after </B> bind time, so we need to create
139      * the column differently.
140      *
141      * @param rcl The RCL to append to.
142      * @param tableNumber The tableNumber for the new ColumnReference
143      *
144      * @return ValueNode The (potentially) modified tree.
145      *
146      * @exception StandardException Thrown on error
147      */

148     public ValueNode replaceAggregatesWithColumnReferences(ResultColumnList rcl, int tableNumber)
149         throws StandardException
150     {
151
152         /*
153         ** This call is idempotent. Do
154         ** the right thing if we have already
155         ** replaced ourselves.
156         */

157         if (generatedRef == null)
158         {
159             String JavaDoc generatedColName;
160             CompilerContext cc = getCompilerContext();
161             generatedColName ="SQLCol" + cc.getNextColumnNumber();
162             generatedRC = (ResultColumn) getNodeFactory().getNode(
163                                             C_NodeTypes.RESULT_COLUMN,
164                                             generatedColName,
165                                             this,
166                                             getContextManager());
167             generatedRC.markGenerated();
168     
169             /*
170             ** Parse time.
171             */

172             if (getTypeServices() == null)
173             {
174                 generatedRef = (ColumnReference) getNodeFactory().getNode(
175                                                 C_NodeTypes.COLUMN_REFERENCE,
176                                                 generatedColName,
177                                                 null,
178                                                 getContextManager());
179             }
180             else
181             {
182                 generatedRef = (ColumnReference) getNodeFactory().getNode(
183                                                 C_NodeTypes.COLUMN_REFERENCE,
184                                                 generatedRC.getName(),
185                                                 null,
186                                                 getContextManager());
187                 generatedRef.setType(this.getTypeServices());
188             }
189             // RESOLVE - unknown nesting level, but not correlated, so nesting levels must be 0
190
generatedRef.setNestingLevel(0);
191             generatedRef.setSourceLevel(0);
192             if (tableNumber != -1)
193             {
194                 generatedRef.setTableNumber(tableNumber);
195             }
196
197             rcl.addResultColumn(generatedRC);
198
199             /*
200             ** Mark the ColumnReference as being generated to replace
201             ** an aggregate
202             */

203             generatedRef.markGeneratedToReplaceAggregate();
204         }
205         else
206         {
207             rcl.addResultColumn(generatedRC);
208         }
209
210         return generatedRef;
211     }
212
213     /**
214      * Get the AggregateDefinition.
215      *
216      * @return The AggregateDefinition
217      */

218     AggregateDefinition getAggregateDefinition()
219     {
220         return uad;
221     }
222
223     /**
224      * Get the generated ResultColumn where this
225      * aggregate now resides after a call to
226      * replaceAggregatesWithColumnReference().
227      *
228      * @return the result column
229      */

230     public ResultColumn getGeneratedRC()
231     {
232         if (SanityManager.DEBUG)
233         {
234             SanityManager.ASSERT(generatedRC != null,
235                 "generatedRC is null. replaceAggregateWithColumnReference() "+
236                 "has not been called on this AggergateNode. Make sure "+
237                 "the node is under a ResultColumn as expected.");
238         }
239                     
240         return generatedRC;
241     }
242
243     /**
244      * Get the generated ColumnReference to this
245      * aggregate after the parent called
246      * replaceAggregatesWithColumnReference().
247      *
248      * @return the column reference
249      */

250     public ColumnReference getGeneratedRef()
251     {
252         if (SanityManager.DEBUG)
253         {
254             SanityManager.ASSERT(generatedRef != null,
255                 "generatedRef is null. replaceAggregateWithColumnReference() "+
256                 "has not been called on this AggergateNode. Make sure "+
257                 "the node is under a ResultColumn as expected.");
258         }
259         return generatedRef;
260     }
261
262     /**
263      * Bind this operator. Determine the type of the subexpression,
264      * and pass that into the UserAggregate.
265      *
266      * @param fromList The query's FROM list
267      * @param subqueryList The subquery list being built as we find SubqueryNodes
268      * @param aggregateVector The aggregate list being built as we find AggregateNodes
269      *
270      * @return The new top of the expression tree.
271      *
272      * @exception StandardException Thrown on error
273      */

274     public ValueNode bindExpression(
275                     FromList fromList,
276                     SubqueryList subqueryList,
277                     Vector JavaDoc aggregateVector)
278             throws StandardException
279     {
280         TypeId outType;
281         TypeId inputType = null;
282         Class JavaDoc inputClass = null;
283         String JavaDoc inputTypeName = null;
284         Class JavaDoc inputInterfaceClass = null;
285         String JavaDoc inputInterfaceName = null;
286         DataTypeDescriptor dts = null;
287         TypeDescriptor resultType = null;
288         ClassFactory cf;
289
290         cf = getClassFactory();
291         classInspector = cf.getClassInspector();
292
293         instantiateAggDef();
294
295         /* Add ourselves to the aggregateVector before we do anything else */
296         aggregateVector.addElement(this);
297
298         super.bindExpression(
299                 fromList, subqueryList,
300                 aggregateVector);
301
302         if (operand != null)
303         {
304             /*
305             ** Make sure that we don't have an aggregate
306             ** IMMEDIATELY below us. Don't search below
307             ** any ResultSetNodes.
308             */

309             HasNodeVisitor visitor = new HasNodeVisitor(this.getClass(), ResultSetNode.class);
310             operand.accept(visitor);
311             if (visitor.hasNode())
312             {
313                 throw StandardException.newException(SQLState.LANG_USER_AGGREGATE_CONTAINS_AGGREGATE,
314                         aggregateName);
315             }
316
317             /*
318             ** Check the type of the operand. Make sure that the user
319             ** defined aggregate can handle the operand datatype.
320             */

321             dts = operand.getTypeServices();
322
323             /* Convert count(nonNullableColumn) to count(*) */
324             if (uad instanceof CountAggregateDefinition &&
325                 !dts.isNullable())
326             {
327                 setOperator(aggregateName);
328                 setMethodName(aggregateName);
329             }
330
331             /*
332             ** If we have a distinct, then the value expression
333             ** MUST implement Orderable because we are going
334             ** to process it using it as part of a sort.
335             */

336             if (distinct)
337             {
338                 /*
339                 ** For now, we check to see if orderable() returns
340                 ** true for this type. In the future we may need
341                 ** to check to see if the type implements Orderable
342                 **
343                 */

344                 if (!operand.getTypeId().orderable(cf))
345                 {
346                     throw StandardException.newException(SQLState.LANG_COLUMN_NOT_ORDERABLE_DURING_EXECUTION,
347                             dts.getTypeId().getSQLTypeName());
348                 }
349
350             }
351
352             /*
353             ** Don't allow an untyped null
354             */

355             if (operand instanceof UntypedNullConstantNode)
356             {
357                 throw StandardException.newException(SQLState.LANG_USER_AGGREGATE_BAD_TYPE_NULL, aggregateName);
358             }
359         }
360
361         /*
362         ** Ask the aggregate definition whether it can handle
363         ** the input datatype. If an exception is thrown,
364         ** barf.
365         */

366         try
367         {
368             aggregatorClassName = new StringBuffer JavaDoc();
369             resultType = uad.getAggregator(dts, aggregatorClassName);
370         } catch (Exception JavaDoc e)
371         {
372             //RESOLVE: would be a good idea to add some additional text to
373
// this error, like during getResultDataType on aggregate x
374
// maybe enhance this error everywhere (seems like execution
375
// should also add some text, at the very least saying during
376
// execution. see Compiltion/Generator/UserExpressionBuilder.java
377
throw StandardException.unexpectedUserException(e);
378         }
379
380         if (resultType == null)
381         {
382             throw StandardException.newException(SQLState.LANG_USER_AGGREGATE_BAD_TYPE,
383                         aggregateName,
384                         operand.getTypeId().getSQLTypeName());
385         }
386
387         checkAggregatorClassName(aggregatorClassName.toString());
388
389         /*
390         ** Try for a built in type matching the
391         ** type name.
392         */

393         TypeId compTypeId = TypeId.getBuiltInTypeId(resultType.getTypeName());
394         /*
395         ** If not built in, it is probably a java type.
396         ** Get the sql type descriptor for that.
397         */

398         if (compTypeId == null)
399         {
400             compTypeId = TypeId.getSQLTypeForJavaType(resultType.getTypeName());
401         }
402
403         /*
404         ** Now set the type. Get a new descriptor
405         ** in case the user returned the same descriptor
406         ** as was passed in.
407         */

408         setType(new DataTypeDescriptor(
409                             compTypeId,
410                             resultType.getPrecision(),
411                             resultType.getScale(),
412                             resultType.isNullable(),
413                             resultType.getMaximumWidth()
414                         )
415                 );
416
417         return this;
418     }
419
420     /*
421     ** Make sure the aggregator class is ok
422     */

423     private void checkAggregatorClassName(String JavaDoc className) throws StandardException
424     {
425         className = verifyClassExist(className, false);
426
427         if (!classInspector.assignableTo(className, "org.apache.derby.iapi.sql.execute.ExecAggregator"))
428         {
429             throw StandardException.newException(SQLState.LANG_BAD_AGGREGATOR_CLASS2,
430                                                     className,
431                                                     aggregateName,
432                                                     operand.getTypeId().getSQLTypeName());
433         }
434     }
435
436         
437     /*
438     ** Instantiate the aggregate definition.
439     */

440     private void instantiateAggDef() throws StandardException
441     {
442         Class JavaDoc theClass = aggregateDefinitionClass;
443
444         // get the class
445
if (theClass == null)
446         {
447             String JavaDoc aggClassName = aggregateDefinitionClassName;
448             aggClassName = verifyClassExist(aggClassName, false);
449
450             try
451             {
452                 theClass = classInspector.getClass(aggClassName);
453             }
454             catch (Throwable JavaDoc t)
455             {
456                 throw StandardException.unexpectedUserException(t);
457             }
458         }
459
460         // get an instance
461
Object JavaDoc instance = null;
462         try
463         {
464             instance = theClass.newInstance();
465         }
466         catch (Throwable JavaDoc t)
467         {
468             throw StandardException.unexpectedUserException(t);
469         }
470
471         if (!(instance instanceof AggregateDefinition))
472         {
473             throw StandardException.newException(SQLState.LANG_INVALID_USER_AGGREGATE_DEFINITION2, aggregateDefinitionClassName);
474         }
475
476         if (instance instanceof MaxMinAggregateDefinition)
477         {
478             MaxMinAggregateDefinition temp = (MaxMinAggregateDefinition)instance;
479             if (aggregateName.equals("MAX"))
480                 temp.setMaxOrMin(true);
481             else
482                 temp.setMaxOrMin(false);
483         }
484
485         if (instance instanceof SumAvgAggregateDefinition)
486         {
487             SumAvgAggregateDefinition temp1 = (SumAvgAggregateDefinition)instance;
488             if (aggregateName.equals("SUM"))
489                 temp1.setSumOrAvg(true);
490             else
491                 temp1.setSumOrAvg(false);
492         }
493
494         this.uad = (AggregateDefinition)instance;
495     
496         setOperator(aggregateName);
497         setMethodName(aggregateDefinitionClassName);
498
499     }
500
501     /**
502      * Indicate whether this aggregate is distinct or not.
503      *
504      * @return true/false
505      */

506     public boolean isDistinct()
507     {
508         return distinct;
509     }
510
511     /**
512      * Get the class that implements that aggregator for this
513      * node.
514      *
515      * @return the class name
516      */

517     public String JavaDoc getAggregatorClassName()
518     {
519         return aggregatorClassName.toString();
520     }
521
522     /**
523      * Get the class that implements that aggregator for this
524      * node.
525      *
526      * @return the class name
527      */

528     public String JavaDoc getAggregateName()
529     {
530         return aggregateName;
531     }
532
533     /**
534      * Get the result column that has a new aggregator.
535      * This aggregator will be fed into the sorter.
536      *
537      * @param dd the data dictionary
538      *
539      * @return the result column. WARNING: it still needs to be bound
540      *
541      * @exception StandardException on error
542      */

543     public ResultColumn getNewAggregatorResultColumn(DataDictionary dd)
544         throws StandardException
545     {
546         String JavaDoc className = aggregatorClassName.toString();
547
548         TypeId compTypeId = TypeId.getSQLTypeForJavaType(className);
549
550         /*
551         ** Create a null of the right type. The proper aggregators
552         ** are created dynamically by the SortObservers
553         */

554         ConstantNode nullNode = getNullNode(
555                             compTypeId,
556                             getContextManager()); // no params
557

558         nullNode.bindExpression(
559                         null, // from
560
null, // subquery
561
null); // aggregate
562

563         /*
564         ** Create a result column with this new node below
565         ** it.
566         */

567         return (ResultColumn) getNodeFactory().getNode(
568                                     C_NodeTypes.RESULT_COLUMN,
569                                     aggregateName,
570                                     nullNode,
571                                     getContextManager());
572     }
573
574
575     /**
576      * Get the aggregate expression in a new result
577      * column.
578      *
579      * @param dd the data dictionary
580      *
581      * @return the result column. WARNING: it still needs to be bound
582      *
583      * @exception StandardException on error
584      */

585     public ResultColumn getNewExpressionResultColumn(DataDictionary dd)
586         throws StandardException
587     {
588         ValueNode node;
589         /*
590         ** Create a result column with the aggrergate operand
591         ** it. If there is no operand, then we have a COUNT(*),
592         ** so we'll have to create a new null node and put
593         ** that in place.
594         */

595         node = (operand == null) ?
596             this.getNewNullResultExpression() :
597             operand;
598
599         return (ResultColumn) getNodeFactory().getNode(
600                                 C_NodeTypes.RESULT_COLUMN,
601                                 "##aggregate expression",
602                                 node,
603                                 getContextManager());
604     }
605
606     /**
607      * Get the null aggregate result expression
608      * column.
609      *
610      * @return the value node
611      *
612      * @exception StandardException on error
613      */

614     public ValueNode getNewNullResultExpression()
615         throws StandardException
616     {
617         /*
618         ** Create a result column with the aggrergate operand
619         ** it.
620         */

621         return getNullNode(this.getTypeId(),
622                             getContextManager());
623     }
624
625     /**
626      * Do code generation for this unary operator. Should
627      * never be called for an aggregate -- it should be converted
628      * into something else by code generation time.
629      *
630      * @param acb The ExpressionClassBuilder for the class we're generating
631      * @param mb The method the code to place the code
632      *
633      * @exception StandardException Thrown on error
634      */

635     public void generateExpression(ExpressionClassBuilder acb,
636                                             MethodBuilder mb)
637         throws StandardException
638     {
639         if (SanityManager.DEBUG)
640         {
641             SanityManager.THROWASSERT("generateExpression() should never "+
642                     "be called on an AggregateNode. "+
643                     "replaceAggregatesWithColumnReferences should have " +
644                     "been called prior to generateExpression");
645         }
646     }
647
648     /**
649      * Print a string ref of this node.
650      *
651      * @return a string representation of this node
652      */

653     public String JavaDoc toString()
654     {
655         if (SanityManager.DEBUG)
656         {
657             return "Aggregate: "+aggregateName+
658                 "\ndistinct: "+distinct+
659                 super.toString();
660         }
661         else
662         {
663             return "";
664         }
665     }
666 }
667
Popular Tags