Skip to content

Commit

Permalink
MONDRIAN: When resolving calculated members and sets, apply scope bef…
Browse files Browse the repository at this point in the history
…ore applying SOLVE_ORDER. This new behavior is compatible with Analysis Services 2005; added property mondrian.rolap.SolveOrderMode to choose old behavior. (Contributed by Timothy Lambert.)

[git-p4: depot-paths = "//open/mondrian/": change = 11099]
  • Loading branch information
julianhyde committed May 22, 2008
1 parent 7d380e9 commit ba06964
Show file tree
Hide file tree
Showing 6 changed files with 717 additions and 214 deletions.
31 changes: 29 additions & 2 deletions doc/configuration.html
Expand Up @@ -10,7 +10,7 @@
-->
<head>
<link rel="stylesheet" type="text/css" href="stylesheet.css"/>
<title>Pentaho Analysis Services: Configuration Guide</title>
<title>Pentaho Analysis Services: Configuration Guide</title>
</head>
<body>
<!-- doc2web start -->
Expand Down Expand Up @@ -455,6 +455,33 @@ <h3>1.1 Property list<a name="Property_list">&nbsp;</a></h3>
</td>
</tr>

<tr>
<td>
<code>
<a href="api/mondrian/olap/MondrianProperties.html#SolveOrderMode">mondrian.rolap.SolveOrderMode</a></code></td>
<td>string</td>
<td>absolute</td>
<td>
<p>Controls solve_order evaluation behavior.</p>

<p>Valid values are:
<ul>
<li><b>absolute</b> - The SOLVE_ORDER value is absolute regardless of
where it is defined; e.g. a query defined calculated member with a SOLVE_ORDER
of 1 always takes precedence over a cube defined value of 2.</li>

<li><b>scoped</b> - Cube calculated members are resolved before any session scope
calculated members, and session scope members are resolved before any query
defined calculation. The SOLVE_ORDER value only applies within the scope in
which it was defined.</li>
</ul></p>

<p>The default behavior (scoped) is compatible with Microsoft SQL Server
Analysis Services 2005 and later. Use {@code absolute} to be compatible
with Analysis Services 2000 and Mondrian 3.0 and earlier.</p>
</td>
</tr>

<tr>
<td colspan="4">

Expand Down Expand Up @@ -1440,4 +1467,4 @@ <h3>MDX and SQL Statement Logging<a name="Statement_Logging"></a></h3>
<!-- doc2web end -->

</body>
</html>
</html>
18 changes: 18 additions & 0 deletions mondrian.properties
Expand Up @@ -434,5 +434,23 @@ mondrian.rolap.iterationLimit=0
#
#mondrian.rolap.EnableRolapCubeMemberCache=true

###############################################################################
# Property that controls solve_order behavior.
#
# Valid values are:
# "absolute" - The SOLVE_ORDER value is absolute regardless of
# where it is defined; e.g. a query defined calculated member with a SOLVE_ORDER
# of 1 always takes precedence over a cube defined value of 2.
# "scoped" - Cube calculated members are resolved before any session scope
# calculated members, and session scope members are resolved before any query
# defined calculation. The SOLVE_ORDER value only applies within the scope in
# which it was defined.
#
# The default is "scoped", and is compatible with Analysis Services 2005 and
# later. Use "absolute" to be compatible with Analysis Services 2000 and
# mondrian-3.0.3 and earlier.
#
#mondrian.rolap.SolveOrderMode=scoped

# End mondrian.properties

41 changes: 41 additions & 0 deletions src/main/mondrian/olap/MondrianProperties.java
Expand Up @@ -1091,6 +1091,47 @@ private void load(final PropertySource source) {
public transient final BooleanProperty EnableRolapCubeMemberCache =
new BooleanProperty(
this, "mondrian.rolap.EnableRolapCubeMemberCache", true);

/**
* Property that controls the behavior of
* {@link Property#SOLVE_ORDER solve order} of calculated members and sets.
*
* <p>Valid values are "absolute" and "scoped" (the default). See
* {@link SolveOrderModeEnum} for details.</p>
*/
public transient final StringProperty SolveOrderMode =
new StringProperty(
this, "mondrian.rolap.SolveOrderMode", SolveOrderModeEnum.ABSOLUTE.name());

/**
* Strategies for applying solve order, exposed via the property
* {@link MondrianProperties#SolveOrderMode}.
*/
public enum SolveOrderModeEnum {

/**
* The SOLVE_ORDER value is absolute regardless of
* where it is defined; e.g. a query defined calculated
* member with a SOLVE_ORDER of 1 always takes precedence
* over a cube defined value of 2.
*
* <p>Compatible with Analysis Services 2000, and default behavior
* up to mondrian-3.0.3.
*/
ABSOLUTE,

/**
* Cube calculated members are resolved before any session
* scope calculated members, and session scope members are
* resolved before any query defined calculation. The
* SOLVE_ORDER value only applies within the scope in which
* it was defined.
*
* <p>Compatible with Analysis Services 2005, and default behavior
* from mondrian-3.0.4 and later.
*/
SCOPED;
}
}

// End MondrianProperties.java
13 changes: 11 additions & 2 deletions src/main/mondrian/olap/Util.java
Expand Up @@ -2222,13 +2222,22 @@ public static <E extends Enum<E>> boolean isValid(Class<E> clazz, E e) {
}

/**
* Looks up an enumeration by name, returns null if not valid.
* Looks up an enumeration by name, returning null if not valid.
*/
public static <E extends Enum<E>> E lookup(Class<E> clazz, String name) {
return lookup(clazz, name, null);
}

/**
* Looks up an enumeration by name, returning a given default value if not
* valid.
*/
public static <E extends Enum<E>> E lookup(
Class<E> clazz, String name, E defaultValue) {
try {
return Enum.valueOf(clazz, name);
} catch (IllegalArgumentException e) {
return null;
return defaultValue;
}
}

Expand Down
177 changes: 162 additions & 15 deletions src/main/mondrian/rolap/RolapEvaluator.java
Expand Up @@ -13,8 +13,10 @@

package mondrian.rolap;
import mondrian.calc.*;
import mondrian.mdx.ResolvedFunCall;
import mondrian.olap.*;
import mondrian.olap.fun.FunUtil;
import mondrian.olap.fun.AggregateFunDef;
import mondrian.rolap.sql.SqlQuery;
import mondrian.resource.MondrianResource;
import mondrian.util.Format;
Expand Down Expand Up @@ -76,6 +78,23 @@ public class RolapEvaluator implements Evaluator {

private final List<Member> slicerMembers;

private final MondrianProperties.SolveOrderModeEnum solveOrderMode =
Util.lookup(
MondrianProperties.SolveOrderModeEnum.class,
MondrianProperties.instance().SolveOrderMode.get().toUpperCase(),
MondrianProperties.SolveOrderModeEnum.ABSOLUTE);

/**
* States of the finite state machine for determining the max solve order
* for the "scoped" behavior.
*/
private enum ScopedMaxSolveOrderFinderState {
START,
AGG_SCOPE,
CUBE_SCOPE,
QUERY_SCOPE
};

/**
* Creates an evaluator.
*
Expand Down Expand Up @@ -877,26 +896,154 @@ private Member peekCalcMember() {
return calcMembers[0];

default:
// Find member with the highest solve order.
Member maxSolveMember = calcMembers[0];
int maxSolve = maxSolveMember.getSolveOrder();
for (int i = 1; i < calcMemberCount; i++) {
Member member = calcMembers[i];
int solve = member.getSolveOrder();
if (solve >= maxSolve) {
// If solve orders tie, the dimension with the lower
// ordinal wins.
if (solve > maxSolve
|| member.getDimension().getOrdinal(root.cube)
< maxSolveMember.getDimension().getOrdinal(root.cube))
{
maxSolve = solve;
// TODO Consider revising employing the Strategy architectural pattern
// for setting up solve order mode handling.

switch (solveOrderMode) {
case ABSOLUTE:
return getAbsoluteMaxSolveOrder(calcMembers);
case SCOPED:
return getScopedMaxSolveOrder(calcMembers);
default:
throw Util.unexpected(solveOrderMode);
}
}
}

/*
* Returns the member with the highest solve order according to AS2000 rules.
* This was the behavior prior to solve order mode being configurable.
*
* <p>The SOLVE_ORDER value is absolute regardless of where it is defined;
* e.g. a query defined calculated member with a SOLVE_ORDER of 1 always takes
* precedence over a cube defined value of 2.
*
* <p>No special consideration is given to the aggregate function.
*/
private Member getAbsoluteMaxSolveOrder(Member [] calcMembers) {
// Find member with the highest solve order.
Member maxSolveMember = calcMembers[0];
int maxSolve = maxSolveMember.getSolveOrder();
for (int i = 1; i < calcMemberCount; i++) {
Member member = calcMembers[i];
int solve = member.getSolveOrder();
if (solve >= maxSolve) {
// If solve orders tie, the dimension with the lower
// ordinal wins.
if (solve > maxSolve
|| member.getDimension().getOrdinal(root.cube)
< maxSolveMember.getDimension().getOrdinal(root.cube)) {
maxSolve = solve;
maxSolveMember = member;
}
}
}

return maxSolveMember;
}

/*
* Returns the member with the highest solve order according to AS2005
* scoping rules.
*
* <p>By default, cube calculated members are resolved before any session
* scope calculated members, and session scope members are resolved before
* any query defined calculation. The SOLVE_ORDER value only applies within
* the scope in which it was defined.
*
* <p>The aggregate function is always applied to base members; i.e. as if
* SOLVE_ORDER was defined to be the lowest value in a given evaluation in a
* SSAS2000 sense.
*/
private Member getScopedMaxSolveOrder(Member [] calcMembers) {

// Finite state machine that determines the member with the highest
// solve order.
Member maxSolveMember = null;
ScopedMaxSolveOrderFinderState state =
ScopedMaxSolveOrderFinderState.START;
for (int i = 0; i < calcMemberCount; i++) {
Member member = calcMembers[i];
switch (state) {
case START:
maxSolveMember = member;
if (foundAggregateFunction(maxSolveMember.getExpression())) {
state = ScopedMaxSolveOrderFinderState.AGG_SCOPE;
} else if (maxSolveMember.isCalculatedInQuery()) {
state = ScopedMaxSolveOrderFinderState.QUERY_SCOPE;
} else {
state = ScopedMaxSolveOrderFinderState.CUBE_SCOPE;
}
break;

case AGG_SCOPE:
if (foundAggregateFunction(member.getExpression())) {
if (member.getSolveOrder() > maxSolveMember.getSolveOrder() ||
(member.getSolveOrder() == maxSolveMember.getSolveOrder() &&
member.getDimension().getOrdinal(root.cube) <
maxSolveMember.getDimension().getOrdinal(root.cube))) {
maxSolveMember = member;
}
} else if (member.isCalculatedInQuery()) {
maxSolveMember = member;
state = ScopedMaxSolveOrderFinderState.QUERY_SCOPE;
} else {
maxSolveMember = member;
state = ScopedMaxSolveOrderFinderState.CUBE_SCOPE;
}
break;

case CUBE_SCOPE:
if (foundAggregateFunction(member.getExpression())) {
continue;
}

if (member.isCalculatedInQuery()) {
maxSolveMember = member;
state = ScopedMaxSolveOrderFinderState.QUERY_SCOPE;
} else if (member.getSolveOrder() > maxSolveMember.getSolveOrder() ||
(member.getSolveOrder() == maxSolveMember.getSolveOrder() &&
member.getDimension().getOrdinal(root.cube) <
maxSolveMember.getDimension().getOrdinal(root.cube))){

maxSolveMember = member;
}
break;

case QUERY_SCOPE:
if (foundAggregateFunction(member.getExpression())) {
continue;
}

if (member.isCalculatedInQuery()) {
if (member.getSolveOrder() > maxSolveMember.getSolveOrder() ||
(member.getSolveOrder() == maxSolveMember.getSolveOrder() &&
member.getDimension().getOrdinal(root.cube) <
maxSolveMember.getDimension().getOrdinal(root.cube))){
maxSolveMember = member;
}
}
break;
}
}

return maxSolveMember;
}

private boolean foundAggregateFunction(Exp exp) {
if (exp instanceof ResolvedFunCall) {
ResolvedFunCall resolvedFunCall = (ResolvedFunCall) exp;
if (resolvedFunCall.getFunDef() instanceof AggregateFunDef) {
return true;
} else {
for (Exp argExp : resolvedFunCall.getArgs()) {
if (foundAggregateFunction(argExp)) {
return true;
}
}
}
return maxSolveMember;
}
return false;
}

private void removeCalcMember(Member previous) {
Expand Down

0 comments on commit ba06964

Please sign in to comment.