/*
// This software is subject to the terms of the Eclipse Public License v1.0
// Agreement, available at the following URL:
// http://www.eclipse.org/legal/epl-v10.html.
// You must accept the terms of that agreement to use this software.
//
// Copyright (c) 2002-2014 Pentaho Corporation.. All rights reserved.
*/
package mondrian.olap.fun;
import mondrian.calc.*;
import mondrian.calc.impl.DelegatingTupleList;
import mondrian.mdx.UnresolvedFunCall;
import mondrian.olap.*;
import mondrian.resource.MondrianResource;
import mondrian.rolap.*;
import java.util.*;
/**
* Abstract base class for all aggregate functions (<code>Aggregate</code>,
* <code>Sum</code>, <code>Avg</code>, et cetera).
*
* @author jhyde
* @since 2005/8/14
*/
public class AbstractAggregateFunDef extends FunDefBase {
public AbstractAggregateFunDef(FunDef dummyFunDef) {
super(dummyFunDef);
}
protected Exp validateArg(
Validator validator, Exp[] args, int i, int category)
{
// If expression cache is enabled, wrap first expression (the set)
// in a function which will use the expression cache.
if (i == 0) {
if (MondrianProperties.instance().EnableExpCache.get()) {
Exp arg = args[0];
if (FunUtil.worthCaching(arg)) {
final Exp cacheCall =
new UnresolvedFunCall(
CacheFunDef.NAME,
Syntax.Function,
new Exp[] {arg});
return validator.validate(cacheCall, false);
}
}
}
return super.validateArg(validator, args, i, category);
}
/**
* Evaluates the list of members or tuples used in computing the aggregate.
* If the measure for aggregation has to ignore unrelated dimensions
* this method will push unrelated dimension members to top level member.
* This behaviour is driven by the ignoreUnrelatedDimensions property
* on a base cube usage specified in the virtual cube.Keeps track of the
* number of iterations that will be required to iterate over the members
* or tuples needed to compute the aggregate within the current context.
* In doing so, also determines if the cross product of all iterations
* across all parent evaluation contexts will exceed the limit set in the
* properties file.
*
* @param listCalc calculator used to evaluate the member list
* @param evaluator current evaluation context
* @return list of evaluated members or tuples
*/
protected static TupleList evaluateCurrentList(
ListCalc listCalc,
Evaluator evaluator)
{
final int savepoint = evaluator.savepoint();
TupleList tuples;
try {
evaluator.setNonEmpty(false);
tuples = listCalc.evaluateList(evaluator);
} finally {
evaluator.restore(savepoint);
}
int currLen = tuples.size();
TupleList dims;
try {
dims = processUnrelatedDimensions(tuples, evaluator);
} finally {
evaluator.restore(savepoint);
}
crossProd(evaluator, currLen);
return dims;
}
protected TupleIterable evaluateCurrentIterable(
IterCalc iterCalc,
Evaluator evaluator)
{
final int savepoint = evaluator.savepoint();
int currLen = 0;
TupleIterable iterable;
try {
evaluator.setNonEmpty(false);
iterable = iterCalc.evaluateIterable(evaluator);
} finally {
evaluator.restore(savepoint);
}
crossProd(evaluator, currLen);
return iterable;
}
private static void crossProd(Evaluator evaluator, int currLen) {
long iterationLimit =
MondrianProperties.instance().IterationLimit.get();
final int productLen = currLen * evaluator.getIterationLength();
if (iterationLimit > 0) {
if (productLen > iterationLimit) {
throw MondrianResource.instance()
.IterationLimitExceeded.ex(iterationLimit);
}
}
evaluator.setIterationLength(currLen);
}
/**
* Pushes unrelated dimensions to the top level member from the given list
* of tuples if the ignoreUnrelatedDimensions property is set on the base
* cube usage in the virtual cube.
*
* <p>If IgnoreMeasureForNonJoiningDimension is set to true and
* ignoreUnrelatedDimensions on CubeUsage is set to false then if a non
* joining dimension exists in the aggregation list then return an empty
* list else return the original list.
*
* @param tuplesForAggregation is a list of members or tuples used in
* computing the aggregate
* @param evaluator Evaluator
* @return list of members or tuples
*/
public static TupleList processUnrelatedDimensions(
TupleList tuplesForAggregation,
Evaluator evaluator)
{
if (tuplesForAggregation.size() == 0) {
return tuplesForAggregation;
}
RolapMember measure = getRolapMeasureForUnrelatedDimCheck(
evaluator, tuplesForAggregation);
if (measure.isCalculated()) {
return tuplesForAggregation;
}
RolapCube virtualCube = (RolapCube) evaluator.getCube();
if (virtualCube.isVirtual()) {
// this should be a safe cast since we've eliminated calcs above
RolapCube baseCube = ((RolapStoredMeasure)measure).getCube();
if (baseCube == null) {
return tuplesForAggregation;
}
if (virtualCube.shouldIgnoreUnrelatedDimensions(baseCube.getName()))
{
return ignoreUnrelatedDimensions(
tuplesForAggregation, baseCube);
} else if (MondrianProperties.instance()
.IgnoreMeasureForNonJoiningDimension.get())
{
return ignoreMeasureForNonJoiningDimension(
tuplesForAggregation, baseCube);
}
}
return tuplesForAggregation;
}
/**
* Returns the measure to use when determining which dimensions
* are unrelated. Most of the time this is the measure in context,
* except 2 cases:
* 1) When a measure is included in a compound slicer
* 2) When one or more measures are included in the first parameter
* of Aggregate.
* e.g. Aggregate( Crossjoin( {Time.[1997]}, {measures.[Unit Sales]})
* In both cases the measure(s) will be present in tuplesForAggregation.
*/
private static RolapMember getRolapMeasureForUnrelatedDimCheck(
Evaluator evaluator, TupleList tuplesForAggregation)
{
RolapMember measure = (RolapMember)evaluator.getMembers()[0];
if (tuplesForAggregation != null
&& tuplesForAggregation.size() > 0)
{
// this looks for the measure in the first tuple, with the
// assumption that there is a single measure in all tuples.
// This assumption is incorrect in the unusual case where
// a set of measures is used as the first param in an aggregate
// function.
for (Member tupMember : tuplesForAggregation.get(0)) {
if (tupMember.isMeasure()) {
measure = (RolapMember)tupMember;
}
}
}
return measure;
}
/**
* If a non joining dimension exists in the aggregation list then return
* an empty list else return the original list.
*
* @param tuplesForAggregation is a list of members or tuples used in
* computing the aggregate
* @param baseCube the cube to scan for nonjoining dimensions.
* @return list of members or tuples
*/
private static TupleList ignoreMeasureForNonJoiningDimension(
TupleList tuplesForAggregation,
RolapCube baseCube)
{
Set<Dimension> nonJoiningDimensions =
nonJoiningDimensions(baseCube, tuplesForAggregation);
if (nonJoiningDimensions.size() > 0) {
return TupleCollections.emptyList(tuplesForAggregation.getArity());
}
return tuplesForAggregation;
}
/**
* Pushes unrelated dimensions to the top level member from the given list
* of tuples if the ignoreUnrelatedDimensions property is set on the base
* cube usage in the virtual cube.
*
* @param tuplesForAggregation is a list of members or tuples used in
* computing the aggregate
* @return list of members or tuples
*/
private static TupleList ignoreUnrelatedDimensions(
TupleList tuplesForAggregation,
RolapCube baseCube)
{
Set<Dimension> nonJoiningDimensions =
nonJoiningDimensions(baseCube, tuplesForAggregation);
final Set<List<Member>> processedTuples =
new LinkedHashSet<List<Member>>(tuplesForAggregation.size());
for (List<Member> tuple : tuplesForAggregation) {
List<Member> tupleCopy = tuple;
for (int j = 0; j < tuple.size(); j++) {
final Member member = tuple.get(j);
if (nonJoiningDimensions.contains(member.getDimension())) {
if (tupleCopy == tuple) {
// Avoid making a copy until we have to change a tuple.
tupleCopy = new ArrayList<Member>(tuple);
}
final Hierarchy hierarchy =
member.getDimension().getHierarchy();
if (hierarchy.hasAll()) {
tupleCopy.set(j, hierarchy.getAllMember());
} else {
tupleCopy.set(j, hierarchy.getDefaultMember());
}
}
}
processedTuples.add(tupleCopy);
}
return new DelegatingTupleList(
tuplesForAggregation.getArity(),
new ArrayList<List<Member>>(
processedTuples));
}
private static Set<Dimension> nonJoiningDimensions(
RolapCube baseCube,
TupleList tuplesForAggregation)
{
List<Member> tuple = tuplesForAggregation.get(0);
return baseCube.nonJoiningDimensions(
tuple.toArray(new Member[tuple.size()]));
}
}
// End AbstractAggregateFunDef.java