Skip to content

Commit

Permalink
MONDRIAN: Implement range operator ":" (feature request #653682)
Browse files Browse the repository at this point in the history
[git-p4: depot-paths = "//open/mondrian/": change = 245]
  • Loading branch information
julianhyde committed Dec 17, 2002
1 parent 8acdb33 commit 147e6c3
Show file tree
Hide file tree
Showing 10 changed files with 147 additions and 95 deletions.
6 changes: 6 additions & 0 deletions src/main/mondrian/olap/Hierarchy.java
Expand Up @@ -55,6 +55,12 @@ void formatMembersXml(
* Looks up a member by its unique name.
**/
Member lookupMemberByUniqueName(String s, boolean failIfNotFound);
/**
* Appends to <code>list</code> all members between <code>startMember</code>
* and <code>endMember</code> (inclusive) which belong to
* <code>level</code>.
*/
void getMemberRange(Level level, Member startMember, Member endMember, List list);
}

// End Hierarchy.java
Expand Down
1 change: 0 additions & 1 deletion src/main/mondrian/olap/Level.java
Expand Up @@ -26,7 +26,6 @@ public interface Level extends OlapElement {
Level getParentLevel();
boolean isAll();
boolean areMembersUnique();
Member[] getPeriodsToDate(Member member);
int getLevelType();
static final int STANDARD = 0;
static final int YEARS = 1;
Expand Down
76 changes: 63 additions & 13 deletions src/main/mondrian/olap/fun/BuiltinFunTable.java
Expand Up @@ -35,6 +35,7 @@ public class BuiltinFunTable extends FunTable {
private HashMap upperName2Resolvers;

private static final Resolver[] emptyResolvers = new Resolver[0];
private static final Vector emptyVector = new Vector();

/**
* Creates a <code>BuiltinFunTable</code>. This method should only be
Expand Down Expand Up @@ -289,7 +290,7 @@ private static Exp convert_(Exp fromExp, int to) {

/**
* Returns whether we can convert an argument to a parameter tyoe.
* @param from argument type
* @param fromExp argument type
* @param to parameter type
* @param conversionCount in/out count of number of conversions performed;
* is incremented if the conversion is non-trivial (for
Expand Down Expand Up @@ -1296,7 +1297,7 @@ public void testAvg(FoodMartTestCase test) {
//todo: testAvgWithNulls
}));
define(new MultiResolver(
"Correlation", "Correlation(<Set>, <Numeric Expression>[, <Numeric Expression>])", "Returns the correlation of two series evaluated over a set.",
"Correlation", "Correlation(<Set>, <Numeric Expression>[, <Numeric Expression>])", "Returns the correlation of two series evaluated over a set.",
new String[]{"fnxN","fnxNN"},
new FunkBase() {
public Object evaluate(Evaluator evaluator, Exp[] args) {
Expand Down Expand Up @@ -1472,7 +1473,7 @@ public void testStdevP(FoodMartTestCase test) {
"STDEVP({[Store].[All Stores].[USA].children},[Measures].[Store Sales])");
test.assertEquals("53746.25874541283", result);
}
}));
}));
define(new MultiResolver(
"Sum", "Sum(<Set>[, <Numeric Expression>])", "Returns the sum of a numeric expression evaluated over a set.",
new String[]{"fnx", "fnxN"},
Expand Down Expand Up @@ -1852,10 +1853,10 @@ public Object evaluate(Evaluator evaluator, Exp[] args) {
new String[]{"fx", "fxm"},
new FunkBase() {
public Object evaluate(Evaluator evaluator, Exp[] args) {
return periodsToDate(
return new Vector(periodsToDate(
evaluator,
evaluator.getCube().getMonthLevel(),
getMemberArg(evaluator, args, 0, false));
getMemberArg(evaluator, args, 0, false)));
}
}));
define(new MultiResolver(
Expand Down Expand Up @@ -2019,18 +2020,18 @@ public void testOrderCrossJoin(FoodMartTestCase test) {
public Object evaluate(Evaluator evaluator, Exp[] args) {
Level level = getLevelArg(evaluator, args, 0, false);
Member member = getMemberArg(evaluator, args, 1, false);
return periodsToDate(evaluator, level, member);
return new Vector(periodsToDate(evaluator, level, member));
}
}));
define(new MultiResolver(
"Qtd", "Qtd([<Member>])", "A shortcut function for the PeriodsToDate function that specifies the level to be Quarter.",
new String[]{"fx", "fxm"},
new FunkBase() {
public Object evaluate(Evaluator evaluator, Exp[] args) {
return periodsToDate(
return new Vector(periodsToDate(
evaluator,
evaluator.getCube().getQuarterLevel(),
getMemberArg(evaluator, args, 0, false));
getMemberArg(evaluator, args, 0, false)));
}
}));
if (false) define(new FunDefBase("StripCalculatedMembers", "StripCalculatedMembers(<Set>)", "Removes calculated members from a set.", "fx*"));
Expand Down Expand Up @@ -2286,24 +2287,73 @@ public void testTopSum(FoodMartTestCase test) {
new String[]{"fx", "fxm"},
new FunkBase() {
public Object evaluate(Evaluator evaluator, Exp[] args) {
return periodsToDate(
return new Vector(periodsToDate(
evaluator,
evaluator.getCube().getWeekLevel(),
getMemberArg(evaluator, args, 0, false));
getMemberArg(evaluator, args, 0, false)));
}
}));
define(new MultiResolver(
"Ytd", "Ytd([<Member>])", "A shortcut function for the PeriodsToDate function that specifies the level to be Year.",
new String[]{"fx", "fxm"},
new FunkBase() {
public Object evaluate(Evaluator evaluator, Exp[] args) {
return periodsToDate(
return new Vector(periodsToDate(
evaluator,
evaluator.getCube().getYearLevel(),
getMemberArg(evaluator, args, 0, false));
getMemberArg(evaluator, args, 0, false)));
}
}));
define(new FunDefBase(":", "<Member>:<Member>", "Infix colon operator returns the set of members between a given pair of members.", "ixmm"));
define(new FunDefBase(
":", "<Member>:<Member>", "Infix colon operator returns the set of members between a given pair of members.", "ixmm") {
// implement FunDef
public Object evaluate(Evaluator evaluator, Exp[] args) {
final Member member0 = getMemberArg(evaluator, args, 0, true);
final Member member1 = getMemberArg(evaluator, args, 1, true);
if (member0.isNull() || member1.isNull()) {
return emptyVector;
}
if (member0.getLevel() != member1.getLevel()) {
throw newEvalException(this, "Members must belong to the same level");
}
return new Vector(FunUtil.memberRange(member0, member1));
}

public void testRange(FoodMartTestCase test) {
final Axis axis = test.executeAxis2("[Time].[1997].[Q1].[2] : [Time].[1997].[Q2].[5]");
String expected = "[Time].[1997].[Q1].[2]" + nl +
"[Time].[1997].[Q1].[3]" + nl +
"[Time].[1997].[Q2].[4]" + nl +
"[Time].[1997].[Q2].[5]"; // not parents
test.assertEquals(expected, test.toString(axis.positions));
}
public void testRangeStartEqualsEnd(FoodMartTestCase test) {
final Axis axis = test.executeAxis2("[Time].[1997].[Q3].[7] : [Time].[1997].[Q3].[7]");
String expected = "[Time].[1997].[Q3].[7]";
test.assertEquals(expected, test.toString(axis.positions));
}
public void testRangeEndBeforeStart(FoodMartTestCase test) {
final Axis axis = test.executeAxis2("[Time].[1997].[Q3].[7] : [Time].[1997].[Q2].[5]");
String expected = "[Time].[1997].[Q2].[5]" + nl +
"[Time].[1997].[Q2].[6]" + nl +
"[Time].[1997].[Q3].[7]"; // same as if reversed
test.assertEquals(expected, test.toString(axis.positions));
}
public void testRangeBetweenDifferentLevelsIsError(FoodMartTestCase test) {
test.assertAxisThrows(
"[Time].[1997].[Q2] : [Time].[1997].[Q2].[5]",
"Members must belong to the same level");
}
public void testRangeBoundedByAll(FoodMartTestCase test) {
final Axis axis = test.executeAxis2("[Gender] : [Gender]");
String expected = "[Gender].[All Gender]";
test.assertEquals(expected, test.toString(axis.positions));
}
public void testRangeBoundedByNull(FoodMartTestCase test) {
final Axis axis = test.executeAxis2("[Gender].[F] : [Gender].[M].NextMember");
test.assertTrue(axis.positions.length == 0);
}
});

// special resolver for the "{...}" operator
define(new ResolverBase(
Expand Down
28 changes: 24 additions & 4 deletions src/main/mondrian/olap/fun/FunUtil.java
Expand Up @@ -617,7 +617,6 @@ private static double _avg(SetWrapper sw) {
return sum / sw.v.size();
}


static Object sum(Evaluator evaluator, Vector members, ExpBase exp) {
SetWrapper sw = evaluateSet(evaluator, members, exp);
if (sw.errorCount > 0) {
Expand Down Expand Up @@ -689,14 +688,35 @@ static int sign(double d1, double d2) {
1;
}

static Vector periodsToDate(
static List periodsToDate(
Evaluator evaluator, Level level, Member member) {
if (member == null) {
member = evaluator.getContext(
level.getHierarchy().getDimension());
}
Member[] members = level.getPeriodsToDate(member);
return toVector(members);
Member m = member;
while (m != null) {
if (m.getLevel() == level) {
break;
}
m = m.getParentMember();
}
ArrayList members = new ArrayList();
level.getHierarchy().getMemberRange(level, m, member, members);
return members;
}

static List memberRange(Member startMember, Member endMember) {
final Level level = startMember.getLevel();
assertTrue(level == endMember.getLevel());
ArrayList members = new ArrayList();
level.getHierarchy().getMemberRange(level, startMember, endMember, members);
if (members.isEmpty()) {
// The result is empty, so maybe the members are reversed. This is
// cheaper than comparing the members before we call getMemberRange.
level.getHierarchy().getMemberRange(level, endMember, startMember, members);
}
return members;
}

/**
Expand Down
49 changes: 18 additions & 31 deletions src/main/mondrian/rolap/CacheMemberReader.java
Expand Up @@ -12,15 +12,14 @@

package mondrian.rolap;
import mondrian.olap.Util;
import mondrian.rolap.sql.SqlQuery;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
* <code>CacheMemberReader</code> implements {@link MemberReader} by reading
* from a pre-populated array of {@link Member}s. The derived class must
* implement {@link #qualifyQuery}.
* from a pre-populated array of {@link mondrian.olap.Member}s.
*
* @author jhyde
* @since 21 December, 2001
Expand Down Expand Up @@ -127,8 +126,7 @@ static RolapMember lookupMember(
return member;
}

public RolapMember[] getRootMembers()
{
public RolapMember[] getRootMembers() {
ArrayList list = new ArrayList();
for (int i = 0; i < members.length; i++) {
if (members[i].getParentUniqueName() == null) {
Expand All @@ -137,9 +135,9 @@ public RolapMember[] getRootMembers()
}
return (RolapMember[]) list.toArray(RolapUtil.emptyMemberArray);
}

public RolapMember[] getMembersInLevel(
RolapLevel level, int startOrdinal, int endOrdinal)
{
RolapLevel level, int startOrdinal, int endOrdinal) {
ArrayList list = new ArrayList();
int levelDepth = level.getDepth();
for (int i = 0; i < members.length; i++) {
Expand All @@ -152,15 +150,14 @@ public RolapMember[] getMembersInLevel(
}
return (RolapMember[]) list.toArray(RolapUtil.emptyMemberArray);
}
public RolapMember[] getMemberChildren(
RolapMember[] parentOlapMembers)
{

public RolapMember[] getMemberChildren(RolapMember[] parentOlapMembers) {
// Find the children by simply scanning the array of all
// members. This won't be efficient when there are a lot of
// members.
ArrayList list = new ArrayList();
for (int i = 0; i < members.length; i++) {
RolapMember member = (RolapMember) members[i];
RolapMember member = members[i];
for (int j = 0; j < parentOlapMembers.length; j++) {
if (member.getParentMember() == parentOlapMembers[j]) {
list.add(member);
Expand All @@ -169,8 +166,8 @@ public RolapMember[] getMemberChildren(
}
return (RolapMember[]) list.toArray(RolapUtil.emptyMemberArray);
}
public RolapMember getLeadMember(RolapMember member, int n)
{

public RolapMember getLeadMember(RolapMember member, int n) {
if (n >= 0) {
for (int ordinal = member.ordinal; ordinal < members.length;
ordinal++) {
Expand All @@ -190,28 +187,18 @@ public RolapMember getLeadMember(RolapMember member, int n)
return (RolapMember) member.getHierarchy().getNullMember();
}
}
public RolapMember[] getPeriodsToDate(
RolapLevel level, RolapMember member)
{
ArrayList list = new ArrayList();
int startOrdinal = -1;
for (RolapMember m = member; m != null; m = (RolapMember) m.getParentMember()) {
if (m.getLevel() == level) {
startOrdinal = m.ordinal;
}
}
if (startOrdinal == -1) {
return new RolapMember[0]; // level not found
}
for (int i = startOrdinal; i <= member.ordinal; i++) {
if (members[i].getLevel() == member.getLevel()) {

public void getMemberRange(
RolapLevel level, RolapMember startMember, RolapMember endMember,
List list) {
for (int i = startMember.ordinal; i <= endMember.ordinal; i++) {
if (members[i].getLevel() == endMember.getLevel()) {
list.add(members[i]);
}
}
return (RolapMember[]) list.toArray(RolapUtil.emptyMemberArray);
}
public int getMemberCount()
{

public int getMemberCount() {
return members.length;
}

Expand Down
7 changes: 6 additions & 1 deletion src/main/mondrian/rolap/MemberReader.java
Expand Up @@ -13,6 +13,8 @@
package mondrian.rolap;
import mondrian.olap.*;

import java.util.List;

/**
* todo:
*
Expand All @@ -31,7 +33,10 @@ interface MemberReader extends MemberSource
* between <code>startOrdinal</code> and <code>endOrdinal</code>. **/
RolapMember[] getMembersInLevel(
RolapLevel level, int startOrdinal, int endOrdinal);
RolapMember[] getPeriodsToDate(RolapLevel level, RolapMember member);
/** Writes all members between <code>startMember</code> and
* <code>endMember</code> into <code>list</code>. */
void getMemberRange(RolapLevel level, RolapMember startMember,
RolapMember endMember, List list);
/** Compares two members according to their order in a prefix ordered
* traversal. If <code>siblingsAreEqual</code>, then two members with the
* same parent will compare equal.
Expand Down
13 changes: 10 additions & 3 deletions src/main/mondrian/rolap/RolapHierarchy.java
Expand Up @@ -14,6 +14,8 @@
import mondrian.olap.*;
import mondrian.rolap.sql.SqlQuery;

import java.util.List;

/**
* <code>RolapHierarchy</code> implements {@link Hierarchy} for a ROLAP database.
*
Expand Down Expand Up @@ -355,10 +357,15 @@ public Member lookupMemberByUniqueName(String uniqueName, boolean failIfNotFound
return memberReader.lookupMember(uniqueName, failIfNotFound);
}

String getAlias()
{
public void getMemberRange(Level level, Member startMember, Member endMember, List list) {
memberReader.getMemberRange((RolapLevel) level,
(RolapMember) startMember, (RolapMember) endMember, list);
}

String getAlias() {
return getName();
}

/**
* Adds to the FROM clause of the query the tables necessary to access the
* members of this hierarchy. If <code>expression</code> is not null, adds
Expand Down Expand Up @@ -450,7 +457,7 @@ HierarchyUsage createUsage(MondrianDef.Relation fact) {
* A <code>RolapNullMember</code> is the null member of its hierarchy.
* Every hierarchy has precisely one. They are yielded by operations such as
* <code>[Gender].[All].ParentMember</code>. Null members are usually omitted
* from sets (in particular, by the "{" ... "}" operator).
* from sets (in particular, in the set constructor operator "{ ... }".
*/
class RolapNullMember extends RolapMember {
RolapNullMember(RolapHierarchy hierarchy) {
Expand Down

0 comments on commit 147e6c3

Please sign in to comment.