Skip to content

Commit

Permalink
MONDRIAN: add helper class CustomizedFunctionTable and
Browse files Browse the repository at this point in the history
tests. Also fix a problem where "*" function needs to look
recursively down the stack for its expected output type.

[git-p4: depot-paths = "//open/mondrian/": change = 11144]
  • Loading branch information
Rushan Chen committed Jun 4, 2008
1 parent ffa8a92 commit 777e365
Show file tree
Hide file tree
Showing 5 changed files with 311 additions and 5 deletions.
9 changes: 8 additions & 1 deletion src/main/mondrian/olap/Query.java
Expand Up @@ -284,6 +284,12 @@ public Validator createValidator() {
return new StackValidator(connection.getSchema().getFunTable());
}

public Validator createValidator(FunTable functionTable) {
StackValidator validator;
validator = new StackValidator(functionTable);
return validator;
}

public Object clone() {
return new Query(
connection,
Expand Down Expand Up @@ -1361,7 +1367,8 @@ private boolean requiresExpression(int n) {
}
} else if (parent instanceof UnresolvedFunCall) {
final UnresolvedFunCall funCall = (UnresolvedFunCall) parent;
if (funCall.getSyntax() == Syntax.Parentheses) {
if (funCall.getSyntax() == Syntax.Parentheses ||
funCall.getFunName() == "*") {
return requiresExpression(n - 1);
} else {
int k = whichArg(funCall, (Exp) stack.get(n));
Expand Down
52 changes: 52 additions & 0 deletions src/main/mondrian/olap/fun/CustomizedFunctionTable.java
@@ -0,0 +1,52 @@
// $Id$
package mondrian.olap.fun;

import java.util.*;

import mondrian.olap.*;

/**
* Interface to build a customized function table, selecting functions from the set of
* supported functions in BuiltInFunTable instance.
*
* @author Rushan Chen
* @version $Id$
*/
public class CustomizedFunctionTable extends FunTableImpl {

Set<String> supportedBuiltInFunctions;
Set<FunDef> specialFunctions;

public CustomizedFunctionTable(Set<String> buildInFunctions) {
supportedBuiltInFunctions = buildInFunctions;
this.specialFunctions = new HashSet<FunDef>();
}

public CustomizedFunctionTable(Set<String> buildInFunctions, Set<FunDef> specialFunctions) {
supportedBuiltInFunctions = buildInFunctions;
this.specialFunctions = specialFunctions;
}

protected void defineFunctions() {
final FunTable builtinFunTable = BuiltinFunTable.instance();

// Includes all the keywords form builtin function table
for (String reservedWord : builtinFunTable.getReservedWords()) {
defineReserved(reservedWord);
}

// Add supported builtin functions
for (Resolver resolver : builtinFunTable.getResolvers()) {
if (supportedBuiltInFunctions.contains(resolver.getName())) {
define(resolver);
}
}

// Add special function definitions
for (FunDef funDef : specialFunctions) {
define(funDef);
}
}
}

// End CustomizedFunctionTable.java
4 changes: 2 additions & 2 deletions src/main/mondrian/olap/fun/ParenthesesFunDef.java
Expand Up @@ -27,9 +27,9 @@
* @since 3 March, 2002
* @version $Id$
*/
class ParenthesesFunDef extends FunDefBase {
public class ParenthesesFunDef extends FunDefBase {
private final int argType;
ParenthesesFunDef(int argType) {
public ParenthesesFunDef(int argType) {
super(
"()",
"(<Expression>)",
Expand Down
230 changes: 230 additions & 0 deletions testsrc/main/mondrian/olap/CustomizedParserTest.java
@@ -0,0 +1,230 @@
/*
// $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.
// Copyright (C) 2004-2007 Julian Hyde and others.
// All Rights Reserved.
// You must accept the terms of that agreement to use this software.
*/
package mondrian.olap;

import mondrian.test.FoodMartTestCase;
import mondrian.olap.fun.*;

import java.util.*;

/**
* Tests a customized MDX Parser.
*
* @author Rushan Chen
* @version $Id$
*/
public class CustomizedParserTest extends FoodMartTestCase {

Parser p = new Parser();
public CustomizedParserTest(String name) {
super(name);
}

CustomizedFunctionTable getCustomizedFunctionTable(Set<String> funNameSet) {
Set<FunDef> specialFunctions = new HashSet<FunDef>();
ParenthesesFunDef specialPerentheseFun = new ParenthesesFunDef(Category.Numeric);
specialFunctions.add(specialPerentheseFun);

CustomizedFunctionTable cftab =
new CustomizedFunctionTable(funNameSet, specialFunctions);
cftab.init();
return cftab;
}

private String wrapExpr(String expr) {
return "with member [Measures].[Foo] as " +
expr +
"\n select from [Sales]";
}

private void checkErrorMsg(Throwable e, String expectedErrorMsg) {
String actualMsg = e.getMessage();
if (e.getCause() != null) {
actualMsg = e.getCause().getMessage();
}
assertEquals(fold(expectedErrorMsg), actualMsg);
}

private Query getParsedQueryForExpr(CustomizedFunctionTable cftab, String expr) {
String mdx = wrapExpr(expr);
Query q = p.parseInternal(getConnection(), mdx, false, cftab, false);
return q;
}

public void testAddition() {
Set<String> functionNameSet = new HashSet<String>();
functionNameSet.add("+");
CustomizedFunctionTable cftab = getCustomizedFunctionTable(functionNameSet);

try {
Query q =
getParsedQueryForExpr(cftab,
"([Measures].[Store Cost] + [Measures].[Unit Sales])");
q.resolve(q.createValidator(cftab));
} catch (Throwable e) {
fail(e.getMessage());
}
}

public void testSubtraction() {
Set<String> functionNameSet = new HashSet<String>();
functionNameSet.add("-");
CustomizedFunctionTable cftab = getCustomizedFunctionTable(functionNameSet);

try {
Query q =
getParsedQueryForExpr(cftab,
"([Measures].[Store Cost] - [Measures].[Unit Sales])");
q.resolve(q.createValidator(cftab));
} catch (Throwable e) {
fail(e.getMessage());
}
}

public void testSingleMultiplication() {
Set<String> functionNameSet = new HashSet<String>();
functionNameSet.add("*");
CustomizedFunctionTable cftab = getCustomizedFunctionTable(functionNameSet);

try {
Query q =
getParsedQueryForExpr(cftab,
"[Measures].[Store Cost] * [Measures].[Unit Sales]");
q.resolve(q.createValidator(cftab));
} catch (Throwable e) {
fail(e.getMessage());
}
}


public void testMultipleMultiplication() {
Set<String> functionNameSet = new HashSet<String>();
functionNameSet.add("*");
CustomizedFunctionTable cftab = getCustomizedFunctionTable(functionNameSet);

try {
Query q =
getParsedQueryForExpr(cftab,
"([Measures].[Store Cost] * [Measures].[Unit Sales] * [Measures].[Store Sales])");
q.resolve(q.createValidator(cftab));
} catch (Throwable e) {
fail(e.getMessage());
}
}

public void testLiterals() {
Set<String> functionNameSet = new HashSet<String>();
functionNameSet.add("+");
CustomizedFunctionTable cftab = getCustomizedFunctionTable(functionNameSet);

try {
Query q =
getParsedQueryForExpr(cftab, "([Measures].[Store Cost] + 10)");
q.resolve(q.createValidator(cftab));
} catch (Throwable e) {
fail(e.getMessage());
}
}

public void testMultiplicationFail() {
Set<String> functionNameSet = new HashSet<String>();
functionNameSet.add("+");
CustomizedFunctionTable cftab = getCustomizedFunctionTable(functionNameSet);

try {
Query q =
getParsedQueryForExpr(cftab, "([Measures].[Store Cost] * [Measures].[Unit Sales])");
q.resolve(q.createValidator(cftab));
// Shouldn't reach here
fail();
} catch (Throwable e) {
checkErrorMsg(e,
"Mondrian Error:No function matches signature '<Member> * <Member>'");
}
}

public void testMixingAttributesFail() {
Set<String> functionNameSet = new HashSet<String>();
functionNameSet.add("+");
CustomizedFunctionTable cftab = getCustomizedFunctionTable(functionNameSet);

try {
Query q =
getParsedQueryForExpr(cftab, "([Measures].[Store Cost] + [Customers].[Name])");
q.resolve(q.createValidator(cftab));
// Shouldn't reach here
fail();
} catch (Throwable e) {
checkErrorMsg(e,
"Mondrian Error:No function matches signature '<Member> + <Level>'");
}
}

public void testCrossJoinFail() {
Set<String> functionNameSet = new HashSet<String>();
functionNameSet.add("+");
functionNameSet.add("-");
functionNameSet.add("*");
functionNameSet.add("/");
CustomizedFunctionTable cftab = getCustomizedFunctionTable(functionNameSet);

try {
Query q =
getParsedQueryForExpr(cftab, "CrossJoin([Measures].[Store Cost], [Measures].[Unit Sales])");
q.resolve(q.createValidator(cftab));
// Shouldn't reach here
fail();
} catch (Throwable e) {
checkErrorMsg(e,
"Mondrian Error:No function matches signature 'CrossJoin(<Member>, <Member>)'");
}
}

public void testMeasureSlicerFail() {
Set<String> functionNameSet = new HashSet<String>();
functionNameSet.add("+");
functionNameSet.add("-");
functionNameSet.add("*");
functionNameSet.add("/");
CustomizedFunctionTable cftab = getCustomizedFunctionTable(functionNameSet);

try {
Query q =
getParsedQueryForExpr(cftab, "([Measures].[Store Cost], [Gender].[F])");
q.resolve(q.createValidator(cftab));
} catch (Throwable e) {
checkErrorMsg(e,
"Mondrian Error:No function matches signature '(<Member>, <Member>)'");
}
}

public void testTupleFail() {
Set<String> functionNameSet = new HashSet<String>();
functionNameSet.add("+");
functionNameSet.add("-");
functionNameSet.add("*");
functionNameSet.add("/");
CustomizedFunctionTable cftab = getCustomizedFunctionTable(functionNameSet);

try {
Query q =
getParsedQueryForExpr(cftab, "([Store].[USA], [Gender].[F])");
q.resolve(q.createValidator(cftab));
// Shouldn't reach here
fail();
} catch (Throwable e) {
checkErrorMsg(e,
"Mondrian Error:No function matches signature '(<Member>, <Member>)'");
}
}

}

//End CustomizedParserTest.java
21 changes: 19 additions & 2 deletions testsrc/main/mondrian/olap/ParserTest.java
Expand Up @@ -9,7 +9,7 @@
*/
package mondrian.olap;

import junit.framework.TestCase;
import mondrian.test.FoodMartTestCase;
import mondrian.olap.fun.BuiltinFunTable;
import mondrian.mdx.UnresolvedFunCall;
import mondrian.mdx.QueryPrintWriter;
Expand All @@ -24,7 +24,7 @@
* @author gjohnson
* @version $Id$
*/
public class ParserTest extends TestCase {
public class ParserTest extends FoodMartTestCase {
public ParserTest(String name) {
super(name);
}
Expand Down Expand Up @@ -297,6 +297,23 @@ public void testCast() {
"CAST((1.0 + 2.0) AS String)");
}

/**
* Verify that calculated measures made of several * operators can resolve them
* correctly.
*/
public void testMultiplication() {
Parser p = new Parser();
final String mdx =
wrapExpr("([Measures].[Unit Sales] * [Measures].[Store Cost] * [Measures].[Store Sales])");

try {
final Query query = p.parseInternal(getConnection(), mdx, false, funTable, false);
query.resolve();
} catch(Throwable e) {
fail(e.getMessage());
}
}

public void testBangFunction() {
// Parser accepts '<id> [! <id>] *' as a function name, but ignores
// all but last name.
Expand Down

0 comments on commit 777e365

Please sign in to comment.