Skip to content

Commit

Permalink
mondrian: support native filter for this condition: IsEmpty(measure).
Browse files Browse the repository at this point in the history
[git-p4: depot-paths = "//open/mondrian/": change = 9453]
  • Loading branch information
Rushan Chen committed Jun 12, 2007
1 parent bf6fcee commit 89db381
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 48 deletions.
2 changes: 1 addition & 1 deletion src/main/mondrian/calc/impl/AbstractCalc.java
Expand Up @@ -174,7 +174,7 @@ public List<Object> getArguments() {
* dimension which an expression depends on, and the default member for
* every dimension which it does not depend on.
*
* <p>The default member is oftne the 'all' member, so this evaluator is
* <p>The default member is often the 'all' member, so this evaluator is
* usually the most efficient context in which to evaluate the expression.
*
* @param calc
Expand Down
51 changes: 30 additions & 21 deletions src/main/mondrian/rolap/RolapNativeRegistry.java
Expand Up @@ -8,8 +8,7 @@
*/
package mondrian.rolap;

import java.util.ArrayList;
import java.util.List;
import java.util.*;

import mondrian.olap.Exp;
import mondrian.olap.FunDef;
Expand All @@ -21,13 +20,19 @@
*/
public class RolapNativeRegistry extends RolapNative {

private List<RolapNative> natives = new ArrayList<RolapNative>();
private Map<String, RolapNative> nativeEvaluatorMap =
new HashMap<String, RolapNative>();

public RolapNativeRegistry() {
super.setEnabled(true);
register(new RolapNativeCrossJoin());
register(new RolapNativeTopCount());
register(new RolapNativeFilter());

/*
* Mondrian functions which might be evaluated natively.
*/
register("NonEmptyCrossJoin".toUpperCase(), new RolapNativeCrossJoin());
register("CrossJoin".toUpperCase(), new RolapNativeCrossJoin());
register("TopCount".toUpperCase(), new RolapNativeTopCount());
register("Filter".toUpperCase(), new RolapNativeFilter());
}

/**
Expand All @@ -40,38 +45,42 @@ public NativeEvaluator createEvaluator(
if (!isEnabled()) {
return null;
}
for (RolapNative rn : natives) {
NativeEvaluator ne = rn.createEvaluator(evaluator, fun, args);
if (ne != null) {
if (listener != null) {
NativeEvent e = new NativeEvent(this, ne);
listener.foundEvaluator(e);
}
return ne;

RolapNative rn = nativeEvaluatorMap.get(fun.getName().toUpperCase());

if (rn == null) {
return null;
}

NativeEvaluator ne = rn.createEvaluator(evaluator, fun, args);

if (ne != null) {
if (listener != null) {
NativeEvent e = new NativeEvent(this, ne);
listener.foundEvaluator(e);
}
}
return null;
return ne;
}

public void register(RolapNative rn) {
natives.add(rn);
public void register(String funName, RolapNative rn) {
nativeEvaluatorMap.put(funName, rn);
}

/** for testing */
void setListener(Listener listener) {
super.setListener(listener);
for (RolapNative rn : natives) {
for (RolapNative rn : nativeEvaluatorMap.values()) {
rn.setListener(listener);
}
}

/** for testing */
void useHardCache(boolean hard) {
for (RolapNative rn : natives) {
for (RolapNative rn : nativeEvaluatorMap.values()) {
rn.useHardCache(hard);
}
}

}

// End RolapNativeRegistry.java
28 changes: 28 additions & 0 deletions src/main/mondrian/rolap/RolapNativeSql.java
Expand Up @@ -365,6 +365,33 @@ public String toString() {
}
}

/**
* compiles an <code>IsEmpty(measure)</code>
* expression into SQL <code>measure is null</code>
*
*/
class IsEmptySqlCompiler extends FunCallSqlCompilerBase {
private final SqlCompiler compiler;

protected IsEmptySqlCompiler(int category, String mdx,
SqlCompiler argumentCompiler) {
super(category, mdx, 1);
this.compiler = argumentCompiler;
}

public String compile(Exp exp) {
String[] args = compileArgs(exp, compiler);
if (args == null) {
return null;
}
return "(" + args[0] + " is null" + ")";
}

public String toString() {
return "IsEmptySqlCompiler[" + mdx + "]";
}
}

/**
* compiles an <code>IIF(cond, val1, val2)</code>
* expression into SQL <code>CASE WHEN cond THEN val1 ELSE val2 END</code>
Expand Down Expand Up @@ -424,6 +451,7 @@ public String compile(Exp exp) {
booleanCompiler.add(new InfixOpSqlCompiler(Category.Logical, ">=", ">=", numericCompiler));
booleanCompiler.add(new InfixOpSqlCompiler(Category.Logical, "=", "=", numericCompiler));
booleanCompiler.add(new InfixOpSqlCompiler(Category.Logical, "<>", "<>", numericCompiler));
booleanCompiler.add(new IsEmptySqlCompiler(Category.Logical, "IsEmpty", numericCompiler));

booleanCompiler.add(new InfixOpSqlCompiler(Category.Logical, "and", "AND", booleanCompiler));
booleanCompiler.add(new InfixOpSqlCompiler(Category.Logical, "or", "OR", booleanCompiler));
Expand Down
170 changes: 144 additions & 26 deletions testsrc/main/mondrian/rolap/NonEmptyTest.java
Expand Up @@ -62,30 +62,30 @@ public NonEmptyTest(String name) {
}

public void testStrMeasure() {
TestContext ctx = TestContext.create(
null,
"<Cube name=\"StrMeasure\"> \n" +
" <Table name=\"promotion\"/> \n" +
" <Dimension name=\"Promotions\"> \n" +
" <Hierarchy hasAll=\"true\" > \n" +
" <Level name=\"Promotion Name\" column=\"promotion_name\" uniqueMembers=\"true\"/> \n" +
" </Hierarchy> \n" +
" </Dimension> \n" +
" <Measure name=\"Media\" column=\"media_type\" aggregator=\"max\" datatype=\"String\"/> \n" +
"</Cube> \n",
null,null,null);

ctx.assertQueryReturns(
"select {[Measures].[Media]} on columns " +
"from [StrMeasure]",
"Axis #0:" + nl +
"{}" + nl +
"Axis #1:" + nl +
"{[Measures].[Media]}" + nl +
"Row #0: TV" + nl

);
}
TestContext ctx = TestContext.create(
null,
"<Cube name=\"StrMeasure\"> \n" +
" <Table name=\"promotion\"/> \n" +
" <Dimension name=\"Promotions\"> \n" +
" <Hierarchy hasAll=\"true\" > \n" +
" <Level name=\"Promotion Name\" column=\"promotion_name\" uniqueMembers=\"true\"/> \n" +
" </Hierarchy> \n" +
" </Dimension> \n" +
" <Measure name=\"Media\" column=\"media_type\" aggregator=\"max\" datatype=\"String\"/> \n" +
"</Cube> \n",
null,null,null);
ctx.assertQueryReturns(
"select {[Measures].[Media]} on columns " +
"from [StrMeasure]",
"Axis #0:" + nl +
"{}" + nl +
"Axis #1:" + nl +
"{[Measures].[Media]}" + nl +
"Row #0: TV" + nl
);
}

public void testBug1515302() {
TestContext ctx = TestContext.create(
Expand Down Expand Up @@ -280,6 +280,118 @@ public void testCmNativeFilter() {
checkNative(32, 8, mdx);
}
}

public void testNonNativeFilterWithNullMeasure() {
String query =
"select Filter([Store].[Store Name].members, " +
" Not ([Measures].[Store Sqft] - [Measures].[Grocery Sqft] < 10000) ) on rows, " +
"{[Measures].[Store Sqft], [Measures].[Grocery Sqft]} on columns " +
"from [Store]";

String result =
"Axis #0:\n" +
"{}\n" +
"Axis #1:\n" +
"{[Measures].[Store Sqft]}\n" +
"{[Measures].[Grocery Sqft]}\n" +
"Axis #2:\n" +
"{[Store].[All Stores].[Mexico].[DF].[Mexico City].[Store 9]}\n" +
"{[Store].[All Stores].[Mexico].[DF].[San Andres].[Store 21]}\n" +
"{[Store].[All Stores].[Mexico].[Yucatan].[Merida].[Store 8]}\n" +
"{[Store].[All Stores].[USA].[CA].[Alameda].[HQ]}\n" +
"{[Store].[All Stores].[USA].[CA].[San Diego].[Store 24]}\n" +
"{[Store].[All Stores].[USA].[WA].[Bremerton].[Store 3]}\n" +
"{[Store].[All Stores].[USA].[WA].[Tacoma].[Store 17]}\n" +
"{[Store].[All Stores].[USA].[WA].[Walla Walla].[Store 22]}\n" +
"{[Store].[All Stores].[USA].[WA].[Yakima].[Store 23]}\n" +
"Row #0: 36,509\n" +
"Row #0: 22,450\n" +
"Row #1: \n" +
"Row #1: \n" +
"Row #2: 30,797\n" +
"Row #2: 20,141\n" +
"Row #3: \n" +
"Row #3: \n" +
"Row #4: \n" +
"Row #4: \n" +
"Row #5: 39,696\n" +
"Row #5: 24,390\n" +
"Row #6: 33,858\n" +
"Row #6: 22,123\n" +
"Row #7: \n" +
"Row #7: \n" +
"Row #8: \n" +
"Row #8: \n";

boolean origNativeFilter =
MondrianProperties.instance().EnableNativeFilter.get();
MondrianProperties.instance().EnableNativeFilter.set(false);

// Get a fresh connection; Otherwise the mondrian property setting
// is not refreshed for this parameter.
Connection conn = getTestContext().getFoodMartConnection(false);
TestContext context = getTestContext(conn);
context.assertQueryReturns(query, fold(result));

MondrianProperties.instance().EnableNativeFilter.set(origNativeFilter);

}

public void testNativeFilterWithNullMeasure() {
// Currently this behaves differently from non-native evaluation.
String query =
"select Filter([Store].[Store Name].members, " +
" Not ([Measures].[Store Sqft] - [Measures].[Grocery Sqft] < 10000) ) on rows, " +
"{[Measures].[Store Sqft], [Measures].[Grocery Sqft]} on columns " +
"from [Store]";

String result =
"Axis #0:\n" +
"{}\n" +
"Axis #1:\n" +
"{[Measures].[Store Sqft]}\n" +
"{[Measures].[Grocery Sqft]}\n" +
"Axis #2:\n" +
"{[Store].[All Stores].[Mexico].[DF].[Mexico City].[Store 9]}\n" +
"{[Store].[All Stores].[Mexico].[Yucatan].[Merida].[Store 8]}\n" +
"{[Store].[All Stores].[USA].[WA].[Bremerton].[Store 3]}\n" +
"{[Store].[All Stores].[USA].[WA].[Tacoma].[Store 17]}\n" +
"Row #0: 36,509\n" +
"Row #0: 22,450\n" +
"Row #1: 30,797\n" +
"Row #1: 20,141\n" +
"Row #2: 39,696\n" +
"Row #2: 24,390\n" +
"Row #3: 33,858\n" +
"Row #3: 22,123\n";

boolean origNativeFilter =
MondrianProperties.instance().EnableNativeFilter.get();
MondrianProperties.instance().EnableNativeFilter.set(true);

// Get a fresh connection; Otherwise the mondrian property setting
// is not refreshed for this parameter.
Connection conn = getTestContext().getFoodMartConnection(false);
TestContext context = getTestContext(conn);
context.assertQueryReturns(query, fold(result));

MondrianProperties.instance().EnableNativeFilter.set(origNativeFilter);

}

public void testNativeFilterNonEmpty() {
if (!MondrianProperties.instance().EnableNativeFilter.get()) {
return;
}
checkNative(
32,
20,
"select Filter(CrossJoin([Store].[Store Name].members, " +
" [Store Type].[Store Type].members), " +
" Not IsEmpty([Measures].[Store Sqft]) ) on rows, " +
"{[Measures].[Store Sqft]} on columns " +
"from [Store]");
}

/**
* getMembersInLevel where Level = (All)
Expand Down Expand Up @@ -1046,7 +1158,7 @@ public void testVirtualCubeNonEmptyCrossJoin()
checkNative(18, 3,
"select " +
"{[Measures].[Units Ordered], [Measures].[Store Sales]} on columns, " +
"nonEmptyCrossJoin([Product].[All Products].children, " +
"NonEmptyCrossJoin([Product].[All Products].children, " +
"[Store].[All Stores].children) on rows " +
"from [Warehouse and Sales]");
}
Expand Down Expand Up @@ -1772,7 +1884,7 @@ public void testIndependentSlicerMemberNonNative() {
MondrianProperties.instance().EnableNativeCrossJoin.set(false);

// Get a fresh connection; Otherwise the mondrian property setting
// is not refreshed.
// is not refreshed for this parameter.
Connection conn = getTestContext().getFoodMartConnection(false);
TestContext context = getTestContext(conn);
context.assertQueryReturns(query, fold(resultNonNative));
Expand All @@ -1799,6 +1911,8 @@ public void testIndependentSlicerMemberNative() {
MondrianProperties.instance().EnableNativeCrossJoin.get();
MondrianProperties.instance().EnableNativeCrossJoin.set(true);

// Get a fresh connection; Otherwise the mondrian property setting
// is not refreshed for this parameter.
Connection conn = getTestContext().getFoodMartConnection(false);
TestContext context = getTestContext(conn);
context.assertQueryReturns(query, fold(resultNative));
Expand All @@ -1824,6 +1938,8 @@ public void testDependentSlicerMemberNonNative() {
MondrianProperties.instance().EnableNativeCrossJoin.get();
MondrianProperties.instance().EnableNativeCrossJoin.set(false);

// Get a fresh connection; Otherwise the mondrian property setting
// is not refreshed for this parameter.
Connection conn = getTestContext().getFoodMartConnection(false);
TestContext context = getTestContext(conn);
context.assertQueryReturns(query, fold(resultNonNative));
Expand All @@ -1849,6 +1965,8 @@ public void testDependentSlicerMemberNative() {
MondrianProperties.instance().EnableNativeCrossJoin.get();
MondrianProperties.instance().EnableNativeCrossJoin.set(true);

// Get a fresh connection; Otherwise the mondrian property setting
// is not refreshed for this parameter.
Connection conn = getTestContext().getFoodMartConnection(false);
TestContext context = getTestContext(conn);
context.assertQueryReturns(query, fold(resultNative));
Expand Down

0 comments on commit 89db381

Please sign in to comment.