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) 2005-2011 Pentaho
008// All Rights Reserved.
009*/
010package mondrian.olap.fun;
011
012import mondrian.calc.*;
013import mondrian.calc.impl.DelegatingTupleList;
014import mondrian.mdx.UnresolvedFunCall;
015import mondrian.olap.*;
016import mondrian.resource.MondrianResource;
017import mondrian.rolap.RolapCube;
018import mondrian.rolap.RolapMember;
019
020import java.util.*;
021
022/**
023 * Abstract base class for all aggregate functions (<code>Aggregate</code>,
024 * <code>Sum</code>, <code>Avg</code>, et cetera).
025 *
026 * @author jhyde
027 * @since 2005/8/14
028 */
029public class AbstractAggregateFunDef extends FunDefBase {
030    public AbstractAggregateFunDef(FunDef dummyFunDef) {
031        super(dummyFunDef);
032    }
033
034    protected Exp validateArg(
035        Validator validator, Exp[] args, int i, int category)
036    {
037        // If expression cache is enabled, wrap first expression (the set)
038        // in a function which will use the expression cache.
039        if (i == 0) {
040            if (MondrianProperties.instance().EnableExpCache.get()) {
041                Exp arg = args[0];
042                if (FunUtil.worthCaching(arg)) {
043                    final Exp cacheCall =
044                        new UnresolvedFunCall(
045                            CacheFunDef.NAME,
046                            Syntax.Function,
047                            new Exp[] {arg});
048                    return validator.validate(cacheCall, false);
049                }
050            }
051        }
052        return super.validateArg(validator, args, i, category);
053    }
054
055    /**
056     * Evaluates the list of members or tuples used in computing the aggregate.
057     * If the measure for aggregation has to ignore unrelated dimensions
058     * this method will push unrelated dimension members to top level member.
059     * This behaviour is driven by the ignoreUnrelatedDimensions property
060     * on a base cube usage specified in the virtual cube.Keeps track of the
061     * number of iterations that will be required to iterate over the members
062     * or tuples needed to compute the aggregate within the current context.
063     * In doing so, also determines if the cross product of all iterations
064     * across all parent evaluation contexts will exceed the limit set in the
065     * properties file.
066     *
067     * @param listCalc  calculator used to evaluate the member list
068     * @param evaluator current evaluation context
069     * @return list of evaluated members or tuples
070     */
071    protected static TupleList evaluateCurrentList(
072        ListCalc listCalc,
073        Evaluator evaluator)
074    {
075        final int savepoint = evaluator.savepoint();
076        TupleList tuples;
077        try {
078            evaluator.setNonEmpty(false);
079            tuples = listCalc.evaluateList(evaluator);
080        } finally {
081            evaluator.restore(savepoint);
082        }
083        int currLen = tuples.size();
084        TupleList dims;
085        try {
086            dims = processUnrelatedDimensions(tuples, evaluator);
087        } finally {
088            evaluator.restore(savepoint);
089        }
090        crossProd(evaluator, currLen);
091        return dims;
092    }
093
094    protected TupleIterable evaluateCurrentIterable(
095        IterCalc iterCalc,
096        Evaluator evaluator)
097    {
098        final int savepoint = evaluator.savepoint();
099        int currLen = 0;
100        TupleIterable iterable;
101        try {
102            evaluator.setNonEmpty(false);
103            iterable = iterCalc.evaluateIterable(evaluator);
104        } finally {
105            evaluator.restore(savepoint);
106        }
107        crossProd(evaluator, currLen);
108        return iterable;
109    }
110
111    private static void crossProd(Evaluator evaluator, int currLen) {
112        long iterationLimit =
113            MondrianProperties.instance().IterationLimit.get();
114        final int productLen = currLen * evaluator.getIterationLength();
115        if (iterationLimit > 0) {
116            if (productLen > iterationLimit) {
117                throw MondrianResource.instance()
118                    .IterationLimitExceeded.ex(iterationLimit);
119            }
120        }
121        evaluator.setIterationLength(currLen);
122    }
123
124    /**
125     * Pushes unrelated dimensions to the top level member from the given list
126     * of tuples if the ignoreUnrelatedDimensions property is set on the base
127     * cube usage in the virtual cube.
128     *
129     * <p>If IgnoreMeasureForNonJoiningDimension is set to true and
130     * ignoreUnrelatedDimensions on CubeUsage is set to false then if a non
131     * joining dimension exists in the aggregation list then return an empty
132     * list else return the original list.
133     *
134     * @param tuplesForAggregation is a list of members or tuples used in
135     * computing the aggregate
136     * @param evaluator Evaluator
137     * @return list of members or tuples
138     */
139    private static TupleList processUnrelatedDimensions(
140        TupleList tuplesForAggregation,
141        Evaluator evaluator)
142    {
143        if (tuplesForAggregation.size() == 0) {
144            return tuplesForAggregation;
145        }
146
147        RolapMember measure = (RolapMember) evaluator.getMembers()[0];
148
149        if (measure.isCalculated()) {
150            return tuplesForAggregation;
151        }
152
153        RolapCube virtualCube = (RolapCube) evaluator.getCube();
154        RolapCube baseCube = (RolapCube) evaluator.getMeasureCube();
155        if (virtualCube.isVirtual() && baseCube != null) {
156            if (virtualCube.shouldIgnoreUnrelatedDimensions(baseCube.getName()))
157            {
158                return ignoreUnrelatedDimensions(
159                    tuplesForAggregation, baseCube);
160            } else if (MondrianProperties.instance()
161                .IgnoreMeasureForNonJoiningDimension.get())
162            {
163                return ignoreMeasureForNonJoiningDimension(
164                    tuplesForAggregation, baseCube);
165            }
166        }
167        return tuplesForAggregation;
168    }
169
170    /**
171     * If a non joining dimension exists in the aggregation list then return
172     * an empty list else return the original list.
173     *
174     * @param tuplesForAggregation is a list of members or tuples used in
175     * computing the aggregate
176     * @param baseCube
177     * @return list of members or tuples
178     */
179    private static TupleList ignoreMeasureForNonJoiningDimension(
180        TupleList tuplesForAggregation,
181        RolapCube baseCube)
182    {
183        Set<Dimension> nonJoiningDimensions =
184            nonJoiningDimensions(baseCube, tuplesForAggregation);
185        if (nonJoiningDimensions.size() > 0) {
186            return TupleCollections.emptyList(tuplesForAggregation.getArity());
187        }
188        return tuplesForAggregation;
189    }
190
191    /**
192     * Pushes unrelated dimensions to the top level member from the given list
193     * of tuples if the ignoreUnrelatedDimensions property is set on the base
194     * cube usage in the virtual cube.
195     *
196     * @param tuplesForAggregation is a list of members or tuples used in
197     * computing the aggregate
198     * @return list of members or tuples
199     */
200    private static TupleList ignoreUnrelatedDimensions(
201        TupleList tuplesForAggregation,
202        RolapCube baseCube)
203    {
204        Set<Dimension> nonJoiningDimensions =
205            nonJoiningDimensions(baseCube, tuplesForAggregation);
206        final Set<List<Member>> processedTuples =
207            new LinkedHashSet<List<Member>>(tuplesForAggregation.size());
208        for (List<Member> tuple : tuplesForAggregation) {
209            List<Member> tupleCopy = tuple;
210            for (int j = 0; j < tuple.size(); j++) {
211                final Member member = tuple.get(j);
212                if (nonJoiningDimensions.contains(member.getDimension())) {
213                    if (tupleCopy == tuple) {
214                        // Avoid making a copy until we have to change a tuple.
215                        tupleCopy = new ArrayList<Member>(tuple);
216                    }
217                    final Hierarchy hierarchy =
218                        member.getDimension().getHierarchy();
219                    if (hierarchy.hasAll()) {
220                        tupleCopy.set(j, hierarchy.getAllMember());
221                    } else {
222                        tupleCopy.set(j, hierarchy.getDefaultMember());
223                    }
224                }
225            }
226            processedTuples.add(tupleCopy);
227        }
228        return new DelegatingTupleList(
229            tuplesForAggregation.getArity(),
230            new ArrayList<List<Member>>(
231                processedTuples));
232    }
233
234    private static Set<Dimension> nonJoiningDimensions(
235        RolapCube baseCube,
236        TupleList tuplesForAggregation)
237    {
238        List<Member> tuple = tuplesForAggregation.get(0);
239        return baseCube.nonJoiningDimensions(
240            tuple.toArray(new Member[tuple.size()]));
241    }
242}
243
244// End AbstractAggregateFunDef.java