001/*
002// This software is subject to the terms of the Eclipse Public License v1.0
003// Agreement, available at the following URL:
004// http://www.eclipse.org/legal/epl-v10.html.
005// You must accept the terms of that agreement to use this software.
006//
007// Copyright (C) 2003-2005 Julian Hyde
008// Copyright (C) 2005-2013 Pentaho
009// All Rights Reserved.
010*/
011package mondrian.rolap;
012
013import mondrian.calc.Calc;
014import mondrian.calc.TupleList;
015import mondrian.olap.*;
016import mondrian.olap.fun.AggregateFunDef;
017import mondrian.olap.fun.FunUtil;
018import mondrian.spi.Dialect;
019import mondrian.spi.Dialect.Datatype;
020
021import java.util.List;
022
023/**
024 * Describes an aggregation operator, such as "sum" or "count".
025 *
026 * @author jhyde
027 * @since Jul 9, 2003
028 */
029public abstract class RolapAggregator
030    extends EnumeratedValues.BasicValue
031    implements Aggregator
032{
033    private static int index = 0;
034
035    public static final RolapAggregator Sum =
036        new RolapAggregator("sum", index++, false) {
037            public Object aggregate(
038                Evaluator evaluator, TupleList members, Calc exp)
039            {
040                return FunUtil.sum(evaluator, members, exp);
041            }
042            public boolean supportsFastAggregates(Dialect.Datatype dataType) {
043                switch (dataType) {
044                case Integer:
045                case Numeric:
046                    return true;
047                default:
048                    return false;
049                }
050            };
051            public Object aggregate(List<Object> rawData, Datatype datatype) {
052                assert rawData.size() > 0;
053                switch (datatype) {
054                case Integer:
055                    int sumInt = Integer.MIN_VALUE;
056                    for (Object data : rawData) {
057                        if (data != null) {
058                            if (sumInt == Integer.MIN_VALUE) {
059                                sumInt = 0;
060                            }
061                            if (data instanceof Double) {
062                                data = ((Double) data).intValue();
063                            }
064                            sumInt += (Integer) data;
065                        }
066                    }
067                    return sumInt == Integer.MIN_VALUE
068                        ? null
069                        : sumInt;
070                case Numeric:
071                    double sumDouble = Double.MIN_VALUE;
072                    for (Object data : rawData) {
073                        if (data != null) {
074                            if (sumDouble == Double.MIN_VALUE) {
075                                sumDouble = 0;
076                            }
077                            sumDouble += ((Number) data).doubleValue();
078                        }
079                    }
080                    return sumDouble == Double.MIN_VALUE
081                        ? null
082                        : sumDouble;
083                default:
084                    throw new MondrianException(
085                        "Aggregator " + this.name
086                        + " does not support datatype" + datatype.name());
087                }
088            }
089        };
090
091    public static final RolapAggregator Count =
092        new RolapAggregator("count", index++, false) {
093            public Aggregator getRollup() {
094                return Sum;
095            }
096
097            public Object aggregate(
098                Evaluator evaluator, TupleList members, Calc exp)
099            {
100                return FunUtil.count(evaluator, members, false);
101            }
102        };
103
104    public static final RolapAggregator Min =
105        new RolapAggregator("min", index++, false) {
106            public Object aggregate(
107                Evaluator evaluator, TupleList members, Calc exp)
108            {
109                return FunUtil.min(evaluator, members, exp);
110            }
111            public boolean supportsFastAggregates(Dialect.Datatype dataType) {
112                switch (dataType) {
113                case Integer:
114                case Numeric:
115                    return true;
116                default:
117                    return false;
118                }
119            };
120            public Object aggregate(List<Object> rawData, Datatype datatype) {
121                assert rawData.size() > 0;
122                switch (datatype) {
123                case Integer:
124                    int minInt = Integer.MAX_VALUE;
125                    for (Object data : rawData) {
126                        if (data != null) {
127                            minInt = Math.min(minInt, (Integer)data);
128                        }
129                    }
130                    return minInt == Integer.MAX_VALUE
131                        ? null
132                        : minInt;
133                case Numeric:
134                    double minDouble = Double.MAX_VALUE;
135                    for (Object data : rawData) {
136                        if (data != null) {
137                            minDouble =
138                                Math.min(
139                                    minDouble,
140                                    ((Number)data).doubleValue());
141                        }
142                    }
143                    return minDouble == Double.MAX_VALUE
144                        ? null
145                        : minDouble;
146                default:
147                    throw new MondrianException(
148                        "Aggregator " + this.name
149                        + " does not support datatype" + datatype.name());
150                }
151            }
152        };
153
154    public static final RolapAggregator Max =
155        new RolapAggregator("max", index++, false) {
156            public Object aggregate(
157                Evaluator evaluator, TupleList members, Calc exp)
158            {
159                return FunUtil.max(evaluator, members, exp);
160            }
161            public boolean supportsFastAggregates(Dialect.Datatype dataType) {
162                switch (dataType) {
163                case Integer:
164                case Numeric:
165                    return true;
166                default:
167                    return false;
168                }
169            };
170            public Object aggregate(List<Object> rawData, Datatype datatype) {
171                assert rawData.size() > 0;
172                switch (datatype) {
173                case Integer:
174                    int maxInt = Integer.MIN_VALUE;
175                    for (Object data : rawData) {
176                        if (data != null) {
177                            maxInt = Math.max(maxInt, (Integer)data);
178                        }
179                    }
180                    return maxInt == Integer.MIN_VALUE
181                        ? null
182                        : maxInt;
183                case Numeric:
184                    double maxDouble = Double.MIN_VALUE;
185                    for (Object data : rawData) {
186                        if (data != null) {
187                            maxDouble =
188                                Math.max(
189                                    maxDouble,
190                                    ((Number)data).doubleValue());
191                        }
192                    }
193
194                    return maxDouble == Double.MIN_VALUE
195                        ? null
196                        : maxDouble;
197                default:
198                    throw new MondrianException(
199                        "Aggregator " + this.name
200                        + " does not support datatype" + datatype.name());
201                }
202            }
203        };
204
205    public static final RolapAggregator Avg =
206        new RolapAggregator("avg", index++, false) {
207            public Aggregator getRollup() {
208                return new RolapAggregator("avg", index, false) {
209                    public Object aggregate(
210                        Evaluator evaluator,
211                        TupleList members,
212                        Calc calc)
213                    {
214                        return AggregateFunDef.avg(evaluator, members, calc);
215                    }
216                };
217            }
218            public Object aggregate(
219                Evaluator evaluator, TupleList members, Calc exp)
220            {
221                return AggregateFunDef.avg(evaluator, members, exp);
222            }
223        };
224
225    public static final RolapAggregator DistinctCount =
226        new RolapAggregator("distinct-count", index++, true) {
227            public Aggregator getRollup() {
228                // Distinct counts cannot always be rolled up, when they can,
229                // it's using Sum.
230                return Sum;
231            }
232
233            public RolapAggregator getNonDistinctAggregator() {
234                return Count;
235            }
236
237            public Object aggregate(
238                Evaluator evaluator, TupleList members, Calc exp)
239            {
240                throw new UnsupportedOperationException();
241            }
242
243            public String getExpression(String operand) {
244                return "count(distinct " + operand + ")";
245            }
246
247            public boolean supportsFastAggregates(
248                mondrian.spi.Dialect.Datatype dataType)
249            {
250                // We can't rollup using the raw data, because this is
251                // a distinct-count operation.
252                return false;
253            };
254        };
255
256    /**
257     * List of all valid aggregation operators.
258     */
259    public static final EnumeratedValues<RolapAggregator> enumeration =
260        new EnumeratedValues<RolapAggregator>(
261            new RolapAggregator[] {Sum, Count, Min, Max, Avg, DistinctCount});
262
263    /**
264     * This is the base class for implementing aggregators over sum and
265     * average columns in an aggregate table. These differ from the above
266     * aggregators in that these require not oly the operand to create
267     * the aggregation String expression, but also, the aggregate table's
268     * fact count column expression.
269     * These aggregators are NOT singletons like the above aggregators; rather,
270     * each is different because of the fact count column expression.
271     */
272    protected static abstract class BaseAggor extends RolapAggregator {
273        protected final String factCountExpr;
274
275        protected BaseAggor(final String name, final String factCountExpr) {
276            super(name, index++, false);
277            this.factCountExpr = factCountExpr;
278        }
279
280        public Object aggregate(
281            Evaluator evaluator, TupleList members, Calc exp)
282        {
283            throw new UnsupportedOperationException();
284        }
285    }
286
287    /**
288     * Aggregator used for aggregate tables implementing the
289     * average aggregator.
290     *
291     * <p>It uses the aggregate table fact_count column
292     * and a sum measure to create the query used to generate an average:
293     * <blockquote>
294     * <code>
295     *    avg == sum(column_sum) / sum(factcount).
296     * </code>
297     * </blockquote>
298     *
299     * <p>If the fact table has both a sum and average over the same column and
300     * the aggregate table only has a sum and fact count column, then the
301     * average aggregator can be generated using this aggregator.
302     */
303    public static class AvgFromSum extends BaseAggor {
304        public AvgFromSum(String factCountExpr) {
305            super("AvgFromSum", factCountExpr);
306        }
307        public String getExpression(String operand) {
308            StringBuilder buf = new StringBuilder(64);
309            buf.append("sum(");
310            buf.append(operand);
311            buf.append(") / sum(");
312            buf.append(factCountExpr);
313            buf.append(')');
314            return buf.toString();
315        }
316    }
317
318    /**
319     * Aggregator used for aggregate tables implementing the
320     * average aggregator.
321     *
322     * <p>It uses the aggregate table fact_count column
323     * and an average measure to create the query used to generate an average:
324     * <blockquote>
325     * <code>
326     *    avg == sum(column_sum * factcount) / sum(factcount).
327     * </code>
328     * </blockquote>
329     *
330     * <p>If the fact table has both a sum and average over the same column and
331     * the aggregate table only has a average and fact count column, then the
332     * average aggregator can be generated using this aggregator.
333     */
334    public static class AvgFromAvg extends BaseAggor {
335        public AvgFromAvg(String factCountExpr) {
336            super("AvgFromAvg", factCountExpr);
337        }
338        public String getExpression(String operand) {
339            StringBuilder buf = new StringBuilder(64);
340            buf.append("sum(");
341            buf.append(operand);
342            buf.append(" * ");
343            buf.append(factCountExpr);
344            buf.append(") / sum(");
345            buf.append(factCountExpr);
346            buf.append(')');
347            return buf.toString();
348        }
349    }
350    /**
351     * This is an aggregator used for aggregate tables implementing the
352     * sum aggregator. It uses the aggregate table fact_count column
353     * and an average measure to create the query used to generate a sum:
354     * <pre>
355     *    sum == sum(column_avg * factcount)
356     * </pre>
357     * If the fact table has both a sum and average over the same column and
358     * the aggregate table only has an average and fact count column, then the
359     * sum aggregator can be generated using this aggregator.
360     */
361    public static class SumFromAvg extends BaseAggor {
362        public SumFromAvg(String factCountExpr) {
363            super("SumFromAvg", factCountExpr);
364        }
365        public String getExpression(String operand) {
366            StringBuilder buf = new StringBuilder(64);
367            buf.append("sum(");
368            buf.append(operand);
369            buf.append(" * ");
370            buf.append(factCountExpr);
371            buf.append(')');
372            return buf.toString();
373        }
374    }
375
376
377
378
379    private final boolean distinct;
380
381    public RolapAggregator(String name, int ordinal, boolean distinct) {
382        super(name, ordinal, null);
383        this.distinct = distinct;
384    }
385
386    public boolean isDistinct() {
387        return distinct;
388    }
389
390    /**
391     * Returns the expression to apply this aggregator to an operand.
392     * For example, <code>getExpression("emp.sal")</code> returns
393     * <code>"sum(emp.sal)"</code>.
394     */
395    public String getExpression(String operand) {
396        StringBuilder buf = new StringBuilder(64);
397        buf.append(name);
398        buf.append('(');
399        if (distinct) {
400            buf.append("distinct ");
401        }
402        buf.append(operand);
403        buf.append(')');
404        return buf.toString();
405    }
406
407    /**
408     * If this is a distinct aggregator, returns the corresponding non-distinct
409     * aggregator, otherwise throws an error.
410     */
411    public RolapAggregator getNonDistinctAggregator() {
412        throw new UnsupportedOperationException();
413    }
414
415    /**
416     * Returns the aggregator used to roll up. By default, aggregators roll up
417     * themselves.
418     */
419    public Aggregator getRollup() {
420        return this;
421    }
422
423    /**
424     * By default, fast rollup is not supported for all classes.
425     */
426    public boolean supportsFastAggregates(Dialect.Datatype dataType) {
427        return false;
428    }
429
430    public Object aggregate(
431        List<Object> rawData,
432        Dialect.Datatype datatype)
433    {
434        throw new UnsupportedOperationException();
435    }
436}
437
438// End RolapAggregator.java