001: /*
002:
003: Derby - Class org.apache.derby.impl.sql.execute.AvgAggregator
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.execute;
023:
024: import org.apache.derby.iapi.services.sanity.SanityManager;
025: import org.apache.derby.iapi.types.NumberDataValue;
026: import org.apache.derby.iapi.error.StandardException;
027: import org.apache.derby.iapi.sql.execute.ExecAggregator;
028: import org.apache.derby.iapi.types.DataValueDescriptor;
029: import org.apache.derby.iapi.types.TypeId;
030:
031: import org.apache.derby.iapi.services.io.StoredFormatIds;
032: import org.apache.derby.iapi.reference.SQLState;
033:
034: import java.io.ObjectOutput;
035: import java.io.ObjectInput;
036: import java.io.IOException;
037:
038: /**
039: Aggregator for AVG(). Extends the SumAggregator and
040: implements a count. Result is then sum()/count().
041: To handle overflow we catch the exception for
042: value out of range, then we swap the holder for
043: the current sum to one that can handle a larger
044: range. Eventually a sum may end up in a SQLDecimal
045: which can handle an infinite range. Once this
046: type promotion has happened, it will not revert back
047: to the original type, even if the sum would fit in
048: a lesser type.
049:
050: */
051: public final class AvgAggregator extends SumAggregator {
052: private long count;
053: private int scale;
054:
055: protected void accumulate(DataValueDescriptor addend)
056: throws StandardException {
057:
058: if (count == 0) {
059:
060: String typeName = addend.getTypeName();
061: if (typeName.equals(TypeId.TINYINT_NAME)
062: || typeName.equals(TypeId.SMALLINT_NAME)
063: || typeName.equals(TypeId.INTEGER_NAME)
064: || typeName.equals(TypeId.LONGINT_NAME)) {
065: scale = 0;
066: } else if (typeName.equals(TypeId.REAL_NAME)
067: || typeName.equals(TypeId.DOUBLE_NAME)) {
068: scale = TypeId.DECIMAL_SCALE;
069: } else {
070: // DECIMAL
071: scale = ((NumberDataValue) addend)
072: .getDecimalValueScale();
073: if (scale < NumberDataValue.MIN_DECIMAL_DIVIDE_SCALE)
074: scale = NumberDataValue.MIN_DECIMAL_DIVIDE_SCALE;
075: }
076: }
077:
078: try {
079:
080: super .accumulate(addend);
081: count++;
082: return;
083:
084: } catch (StandardException se) {
085:
086: if (!se.getMessageId().equals(
087: SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE))
088: throw se;
089: }
090:
091: /*
092: Sum is out of range so promote
093:
094: TINYINT,SMALLINT -->> INTEGER
095:
096: INTEGER -->> BIGINT
097:
098: REAL -->> DOUBLE PRECISION
099:
100: others -->> DECIMAL
101: */
102:
103: // this code creates data type objects directly, it is anticipating
104: // the time they move into the defined api of the type system. (djd).
105: String typeName = value.getTypeName();
106:
107: DataValueDescriptor newValue;
108:
109: if (typeName.equals(TypeId.INTEGER_NAME)) {
110: newValue = new org.apache.derby.iapi.types.SQLLongint();
111: } else if (typeName.equals(TypeId.TINYINT_NAME)
112: || typeName.equals(TypeId.SMALLINT_NAME)) {
113: newValue = new org.apache.derby.iapi.types.SQLInteger();
114: } else if (typeName.equals(TypeId.REAL_NAME)) {
115: newValue = new org.apache.derby.iapi.types.SQLDouble();
116: } else {
117: TypeId decimalTypeId = TypeId
118: .getBuiltInTypeId(java.sql.Types.DECIMAL);
119: newValue = decimalTypeId.getNull();
120: }
121:
122: newValue.setValue(value);
123: value = newValue;
124:
125: accumulate(addend);
126: }
127:
128: public void merge(ExecAggregator addend) throws StandardException {
129: AvgAggregator otherAvg = (AvgAggregator) addend;
130:
131: // if I haven't been used take the other.
132: if (count == 0) {
133: count = otherAvg.count;
134: value = otherAvg.value;
135: scale = otherAvg.scale;
136: return;
137: }
138:
139: // Don't bother merging if the other is a NULL value aggregate.
140: /* Note:Beetle:5346 fix change the sort to be High, that makes
141: * the neccessary for the NULL check because after the change
142: * addend could have a NULL value even on distincts unlike when
143: * NULLs were sort order Low, because by sorting NULLs Low
144: * they happens to be always first row which makes it as
145: * aggreagte result object instead of addends.
146: * Query that will fail without the following check:
147: * select avg(a) , count(distinct a) from t1;
148: */
149: if (otherAvg.value != null) {
150: // subtract one here as the accumulate will add one back in
151: count += (otherAvg.count - 1);
152: accumulate(otherAvg.value);
153: }
154: }
155:
156: /**
157: * Return the result of the aggregation. If the count
158: * is zero, then we haven't averaged anything yet, so
159: * we return null. Otherwise, return the running
160: * average as a double.
161: *
162: * @return null or the average as Double
163: */
164: public DataValueDescriptor getResult() throws StandardException {
165: if (count == 0) {
166: return null;
167: }
168:
169: NumberDataValue sum = (NumberDataValue) value;
170: NumberDataValue avg = (NumberDataValue) value.getNewNull();
171:
172: if (count > (long) Integer.MAX_VALUE) {
173: // TINYINT, SMALLINT, INTEGER implement arithmetic using integers
174: // If the sum is still represented as a TINYINT, SMALLINT or INTEGER
175: // we cannot let their int based arithmetic handle it, since they
176: // will perform a getInt() on the long value which will truncate the long value.
177: // One solution would be to promote the sum to a SQLLongint, but its value
178: // will be less than or equal to Integer.MAX_VALUE, so the average will be 0.
179: String typeName = sum.getTypeName();
180:
181: if (typeName.equals(TypeId.INTEGER_NAME)
182: || typeName.equals(TypeId.TINYINT_NAME)
183: || typeName.equals(TypeId.SMALLINT_NAME)) {
184: avg.setValue(0);
185: return avg;
186: }
187: }
188:
189: NumberDataValue countv = new org.apache.derby.iapi.types.SQLLongint(
190: count);
191: sum.divide(sum, countv, avg, scale);
192:
193: return avg;
194: }
195:
196: /**
197: */
198: public ExecAggregator newAggregator() {
199: return new AvgAggregator();
200: }
201:
202: /////////////////////////////////////////////////////////////
203: //
204: // EXTERNALIZABLE INTERFACE
205: //
206: /////////////////////////////////////////////////////////////
207: /**
208: *
209: * @exception IOException on error
210: */
211: public void writeExternal(ObjectOutput out) throws IOException {
212: super .writeExternal(out);
213: out.writeLong(count);
214: out.writeInt(scale);
215: }
216:
217: /**
218: * @see java.io.Externalizable#readExternal
219: *
220: * @exception IOException on error
221: */
222: public void readExternal(ObjectInput in) throws IOException,
223: ClassNotFoundException {
224: super .readExternal(in);
225: count = in.readLong();
226: scale = in.readInt();
227: }
228:
229: /////////////////////////////////////////////////////////////
230: //
231: // FORMATABLE INTERFACE
232: //
233: /////////////////////////////////////////////////////////////
234: /**
235: * Get the formatID which corresponds to this class.
236: *
237: * @return the formatID of this class
238: */
239: public int getTypeFormatId() {
240: return StoredFormatIds.AGG_AVG_V01_ID;
241: }
242: }
|