Skip to content

Commit

Permalink
MONDRIAN: Implement 'DrilldownMember'.
Browse files Browse the repository at this point in the history
[git-p4: depot-paths = "//open/mondrian/": change = 2919]
  • Loading branch information
julianhyde committed Dec 7, 2004
1 parent b45890e commit 9d12d14
Show file tree
Hide file tree
Showing 3 changed files with 239 additions and 14 deletions.
38 changes: 29 additions & 9 deletions src/main/mondrian/olap/fun/BuiltinFunTable.java
Expand Up @@ -18,19 +18,20 @@

/**
* <code>BuiltinFunTable</code> contains a list of all built-in MDX functions.
* <p>
* Note: Boolean expressions return either Boolean.TRUE or Boolean.FALSE or null. null
* is returned, if the expression can not be evaluated because some values have not
* been loaded from database yet.
*
* <p>Note: Boolean expressions return {@link Boolean#TRUE},
* {@link Boolean#FALSE} or null. null is returned if the expression can not be
* evaluated because some values have not been loaded from database yet.</p>
*
* @author jhyde
* @since 26 February, 2002
* @version $Id$
**/
public class BuiltinFunTable extends FunTable {
/** Maps the upper-case name of a function plus its {@link Syntax} to an
* array of {@link Resolver}s for that name. **/
/**
* Maps the upper-case name of a function plus its {@link Syntax} to an
* array of {@link Resolver} objects for that name.
*/
private HashMap mapNameToResolvers;

private static final Resolver[] emptyResolvers = new Resolver[0];
Expand Down Expand Up @@ -1390,7 +1391,26 @@ public Object evaluate(Evaluator evaluator, Exp[] args) {

if (false) define(new FunDefBase("DrilldownLevelBottom", "DrilldownLevelBottom(<Set>, <Count>[, [<Level>][, <Numeric Expression>]])", "Drills down the bottom N members of a set, at a specified level, to one level below.", "fx*"));
if (false) define(new FunDefBase("DrilldownLevelTop", "DrilldownLevelTop(<Set>, <Count>[, [<Level>][, <Numeric Expression>]])", "Drills down the top N members of a set, at a specified level, to one level below.", "fx*"));
if (false) define(new FunDefBase("DrilldownMember", "DrilldownMember(<Set1>, <Set2>[, RECURSIVE])", "Drills down the members in a set that are present in a second specified set.", "fx*"));

define(new MultiResolver(
"Hierarchize", "Hierarchize(<Set>[, POST])", "Orders the members of a set in a hierarchy.",
new String[] {"fxx", "fxxy"}) {
protected FunDef createFunDef(Exp[] args, FunDef dummyFunDef) {
String order = getLiteralArg(args, 1, "PRE", new String[] {"PRE", "POST"}, dummyFunDef);
final boolean post = order.equals("POST");
return new FunDefBase(dummyFunDef) {
public Object evaluate(Evaluator evaluator, Exp[] args) {
List members = (List) getArg(evaluator, args, 0);
hierarchize(members, post);
return members;
}
};
}
});

defineReserved(DrilldownMemberFunDef.reservedNames);
define(new DrilldownMemberFunDef.Resolver());

if (false) define(new FunDefBase("DrilldownMemberBottom", "DrilldownMemberBottom(<Set1>, <Set2>, <Count>[, [<Numeric Expression>][, RECURSIVE]])", "Like DrilldownMember except that it includes only the bottom N children.", "fx*"));
if (false) define(new FunDefBase("DrilldownMemberTop", "DrilldownMemberTop(<Set1>, <Set2>, <Count>[, [<Numeric Expression>][, RECURSIVE]])", "Like DrilldownMember except that it includes only the top N children.", "fx*"));
if (false) define(new FunDefBase("DrillupLevel", "DrillupLevel(<Set>[, <Level>])", "Drills up the members of a set that are below a specified level.", "fx*"));
Expand Down Expand Up @@ -2587,8 +2607,8 @@ private static boolean isConstantHierarchy(Exp typeArg) {
}

/**
* Get a read-only version of the name-to-resolvers map. Used by the testing
* framework
* Returns a read-only version of the name-to-resolvers map. Used by the
* testing framework.
*/
protected static Map getNameToResolversMap() {
return Collections.unmodifiableMap(((BuiltinFunTable)instance()).mapNameToResolvers);
Expand Down
140 changes: 140 additions & 0 deletions src/main/mondrian/olap/fun/DrilldownMemberFunDef.java
@@ -0,0 +1,140 @@
/*
// $Id$
// This software is subject to the terms of the Common Public License
// Agreement, available at the following URL:
// http://www.opensource.org/licenses/cpl.html.
// (C) Copyright 2004-2004 Julian Hyde
// All Rights Reserved.
// You must accept the terms of that agreement to use this software.
*/
package mondrian.olap.fun;

import mondrian.olap.*;

import java.util.HashSet;
import java.util.List;
import java.util.ArrayList;

/**
* Definition of the "DrilldownMember" MDX function.
*
* @author Grzegorz Lojek
* @since 6 December, 2004
* @version $Id$
*/
class DrilldownMemberFunDef extends FunDefBase {
private final boolean recursive;
static final String[] reservedNames = new String[] {"RECURSIVE"};

DrilldownMemberFunDef(FunDef funDef, boolean recursive) {
super(funDef);
this.recursive = recursive;
}

/**
* Drills down an element.
*
* Algorithm: If object is present in a_hsSet1 then adds to result children
* of the object. If flag a_bRecursive is set then this method is called
* recursively for the children.
*
* @param element Element of a set, can be either {@link Member} or
* {@link Member}[]
*
*
*/
protected void drillDownObj(Evaluator evaluator,
Object element,
HashSet memberSet,
List resultList)
{
if (null == element) {
return;
}

Member m = null;
int k = -1;
if (element instanceof Member) {
if (!memberSet.contains(element)) {
return;
}
m = (Member) element;
} else {
Util.assertTrue(element instanceof Member[]);
Member[] members = (Member[]) element;
for (int j = 0; j < members.length; j++) {
Member member = members[j];
if (memberSet.contains(member)) {
k = j;
m = member;
break;
}
}
if (k == -1) {
return;
}
}

Member[] children = evaluator.getSchemaReader().getMemberChildren(m);
for (int j = 0; j < children.length; j++) {
Object objNew;
if (k < 0) {
objNew = children[j];
} else {
Member[] members = (Member[]) ((Member[]) element).clone();
members[k] = children[j];
objNew = members;
}

resultList.add(objNew);
if (recursive) {
drillDownObj(evaluator, objNew, memberSet, resultList);
}
}
}

public Object evaluate(Evaluator evaluator, Exp[] args) {
// List of members=Set of members, List of member arrays=set of tuples
List v0 = (List) getArg(evaluator, args, 0);
List v1 = (List) getArg(evaluator, args, 1);

if (null == v0 ||
v0.isEmpty() ||
null == v1 ||
v1.isEmpty()) {
return v0;
}

HashSet set1 = new HashSet();
set1.addAll(v1);

List result = new ArrayList();
int i = 0, n = v0.size();
while (i < n) {
Object o = v0.get(i++);
result.add(o);
drillDownObj(evaluator, o, set1, result);
}
return result;
}

/**
* Resolves calls to the "DrilldownMember" function.
*/
static class Resolver extends MultiResolver {
public Resolver() {
super("DrilldownMember",
"DrilldownMember(<Set1>, <Set2>[, RECURSIVE])",
"Drills down the members in a set that are present in a second specified set.",
new String[]{"fxxx", "fxxxy"});
}

protected FunDef createFunDef(Exp[] args, FunDef dummyFunDef) {
boolean recursive = getLiteralArg(args, 2, "", reservedNames,
dummyFunDef).equals("RECURSIVE");
return new DrilldownMemberFunDef(dummyFunDef, recursive);
}
}
}

// End DrilldownMemberFunDef.java
75 changes: 70 additions & 5 deletions testsrc/main/mondrian/olap/fun/FunctionTest.java
Expand Up @@ -430,32 +430,97 @@ public void testDimensionDefaultMember() {
Assert.assertEquals("Unit Sales", member.getName());
}

public void testDrillDownLevel() throws Exception {
public void testDrilldownLevel() throws Exception {
// Expect all children of USA
mTest.assertAxisReturns("DrillDownLevel({[Store].[USA]}, [Store].[Store Country])",
mTest.assertAxisReturns("DrilldownLevel({[Store].[USA]}, [Store].[Store Country])",
"[Store].[All Stores].[USA]" + nl +
"[Store].[All Stores].[USA].[CA]" + nl +
"[Store].[All Stores].[USA].[OR]" + nl +
"[Store].[All Stores].[USA].[WA]");

// Expect same set, because [USA] is already drilled
mTest.assertAxisReturns("DrillDownLevel({[Store].[USA], [Store].[USA].[CA]}, [Store].[Store Country])",
mTest.assertAxisReturns("DrilldownLevel({[Store].[USA], [Store].[USA].[CA]}, [Store].[Store Country])",
"[Store].[All Stores].[USA]" + nl +
"[Store].[All Stores].[USA].[CA]");

// Expect drill, because [USA] isn't already drilled. You can't
// drill down on [CA] and get to [USA]
mTest.assertAxisReturns("DrillDownLevel({[Store].[USA].[CA],[Store].[USA]}, [Store].[Store Country])",
mTest.assertAxisReturns("DrilldownLevel({[Store].[USA].[CA],[Store].[USA]}, [Store].[Store Country])",
"[Store].[All Stores].[USA].[CA]" + nl +
"[Store].[All Stores].[USA]" + nl +
"[Store].[All Stores].[USA].[CA]" + nl +
"[Store].[All Stores].[USA].[OR]" + nl +
"[Store].[All Stores].[USA].[WA]");

mTest.assertThrows("select DrillDownLevel({[Store].[USA].[CA],[Store].[USA]}, , 0) on columns from [Sales]",
mTest.assertThrows("select DrilldownLevel({[Store].[USA].[CA],[Store].[USA]}, , 0) on columns from [Sales]",
"Syntax error");
}


public void testDrilldownMember() throws Exception {

// Expect all children of USA
mTest.assertAxisReturns("DrilldownMember({[Store].[USA]}, {[Store].[USA]})",
"[Store].[All Stores].[USA]" + nl +
"[Store].[All Stores].[USA].[CA]" + nl +
"[Store].[All Stores].[USA].[OR]" + nl +
"[Store].[All Stores].[USA].[WA]");

// Expect all children of USA.CA and USA.OR
mTest.assertAxisReturns("DrilldownMember({[Store].[USA].[CA], [Store].[USA].[OR]}, "+
"{[Store].[USA].[CA], [Store].[USA].[OR], [Store].[USA].[WA]})",
"[Store].[All Stores].[USA].[CA]" + nl +
"[Store].[All Stores].[USA].[CA].[Alameda]" + nl +
"[Store].[All Stores].[USA].[CA].[Beverly Hills]" + nl +
"[Store].[All Stores].[USA].[CA].[Los Angeles]" + nl +
"[Store].[All Stores].[USA].[CA].[San Diego]" + nl +
"[Store].[All Stores].[USA].[CA].[San Francisco]" + nl +
"[Store].[All Stores].[USA].[OR]" + nl +
"[Store].[All Stores].[USA].[OR].[Portland]" + nl +
"[Store].[All Stores].[USA].[OR].[Salem]");


// Second set is empty
mTest.assertAxisReturns("DrilldownMember({[Store].[USA]}, {})",
"[Store].[All Stores].[USA]");

// Drill down a leaf member
mTest.assertAxisReturns("DrilldownMember({[Store].[All Stores].[USA].[CA].[San Francisco].[Store 14]}, "+
"{[Store].[All Stores].[USA].[CA].[San Francisco].[Store 14]})",
"[Store].[All Stores].[USA].[CA].[San Francisco].[Store 14]");

// Complex case with option recursive
mTest.assertAxisReturns("DrilldownMember({[Store].[All Stores].[USA]}, "+
"{[Store].[All Stores].[USA], [Store].[All Stores].[USA].[CA], "+
"[Store].[All Stores].[USA].[CA].[San Diego], [Store].[All Stores].[USA].[WA]}, "+
"RECURSIVE)",
"[Store].[All Stores].[USA]" + nl +
"[Store].[All Stores].[USA].[CA]" + nl +
"[Store].[All Stores].[USA].[CA].[Alameda]" + nl +
"[Store].[All Stores].[USA].[CA].[Beverly Hills]" + nl +
"[Store].[All Stores].[USA].[CA].[Los Angeles]" + nl +
"[Store].[All Stores].[USA].[CA].[San Diego]" + nl +
"[Store].[All Stores].[USA].[CA].[San Diego].[Store 24]" + nl +
"[Store].[All Stores].[USA].[CA].[San Francisco]" + nl +
"[Store].[All Stores].[USA].[OR]" + nl +
"[Store].[All Stores].[USA].[WA]" + nl +
"[Store].[All Stores].[USA].[WA].[Bellingham]" + nl +
"[Store].[All Stores].[USA].[WA].[Bremerton]" + nl +
"[Store].[All Stores].[USA].[WA].[Seattle]" + nl +
"[Store].[All Stores].[USA].[WA].[Spokane]" + nl +
"[Store].[All Stores].[USA].[WA].[Tacoma]" + nl +
"[Store].[All Stores].[USA].[WA].[Walla Walla]" + nl +
"[Store].[All Stores].[USA].[WA].[Yakima]");

// Sets of tuples
mTest.assertAxisReturns("DrilldownMember({([Store Type].[Supermarket], [Store].[USA])}, {[Store].[USA]})",
"{[Store Type].[All Store Types].[Supermarket], [Store].[All Stores].[USA]}" + nl +
"{[Store Type].[All Store Types].[Supermarket], [Store].[All Stores].[USA].[CA]}" + nl +
"{[Store Type].[All Store Types].[Supermarket], [Store].[All Stores].[USA].[OR]}" + nl +
"{[Store Type].[All Store Types].[Supermarket], [Store].[All Stores].[USA].[WA]}");
}


public void testFirstChildFirstInLevel() {
Member member = mTest.executeAxis("[Time].[1997].[Q4].FirstChild");
Assert.assertEquals("10", member.getName());
Expand Down

0 comments on commit 9d12d14

Please sign in to comment.