Skip to content

Commit

Permalink
MONDRIAN: Fix bug MONDRIAN-588, "UDF returning List works under 2.4, …
Browse files Browse the repository at this point in the history
…fails under 3.1.1".

    Also add test cases for other UDF functionality.

[git-p4: depot-paths = "//open/mondrian/": change = 12979]
  • Loading branch information
julianhyde committed Aug 2, 2009
1 parent c651bd2 commit 892e49e
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 7 deletions.
10 changes: 10 additions & 0 deletions src/main/mondrian/olap/Util.java
Expand Up @@ -2474,6 +2474,16 @@ public static UserDefinedFunction createUdf(
Constructor<?> constructor;
Object[] args = {};

// 0. Check that class is public and top-level or static.
if (!Modifier.isPublic(udfClass.getModifiers())
|| (udfClass.getEnclosingClass() != null
&& !Modifier.isStatic(udfClass.getModifiers())))
{
throw MondrianResource.instance().UdfClassMustBePublicAndStatic.ex(
functionName,
className);
}

// 1. Look for a constructor "public Udf(String name)".
try {
constructor = udfClass.getConstructor(String.class);
Expand Down
47 changes: 40 additions & 7 deletions src/main/mondrian/olap/fun/UdfResolver.java
Expand Up @@ -14,6 +14,7 @@
import mondrian.spi.UserDefinedFunction;
import mondrian.calc.*;
import mondrian.calc.impl.GenericCalc;
import mondrian.calc.impl.AbstractListCalc;
import mondrian.mdx.ResolvedFunCall;

import java.util.List;
Expand Down Expand Up @@ -69,9 +70,9 @@ public FunDef getFunDef() {
}

public FunDef resolve(
Exp[] args,
Validator validator,
List<Conversion> conversions)
Exp[] args,
Validator validator,
List<Conversion> conversions)
{
final Type[] parameterTypes = udf.getParameterTypes();
if (args.length != parameterTypes.length) {
Expand Down Expand Up @@ -147,19 +148,23 @@ public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) {
UserDefinedFunction udf2 =
Util.createUdf(
udf.getClass(), udf.getName());
return new CalcImpl(call, calcs, udf2, expCalcs);
if (call.getType() instanceof SetType) {
return new ListCalcImpl(call, calcs, udf2, expCalcs);
} else {
return new ScalarCalcImpl(call, calcs, udf2, expCalcs);
}
}
}

/**
* Expression which evaluates a user-defined function.
* Expression that evaluates a scalar user-defined function.
*/
private static class CalcImpl extends GenericCalc {
private static class ScalarCalcImpl extends GenericCalc {
private final Calc[] calcs;
private final UserDefinedFunction udf;
private final UserDefinedFunction.Argument[] args;

public CalcImpl(
public ScalarCalcImpl(
ResolvedFunCall call,
Calc[] calcs,
UserDefinedFunction udf,
Expand All @@ -185,6 +190,34 @@ public boolean dependsOn(Dimension dimension) {
}
}

/**
* Expression that evaluates a list user-defined function.
*/
private static class ListCalcImpl extends AbstractListCalc {
private final UserDefinedFunction udf;
private final UserDefinedFunction.Argument[] args;

public ListCalcImpl(
ResolvedFunCall call,
Calc[] calcs,
UserDefinedFunction udf,
UserDefinedFunction.Argument[] args)
{
super(call, calcs);
this.udf = udf;
this.args = args;
}

public List evaluateList(Evaluator evaluator) {
return (List) udf.execute(evaluator, args);
}

public boolean dependsOn(Dimension dimension) {
// Be pessimistic. This effectively disables expression caching.
return true;
}
}

/**
* Wrapper around a {@link Calc} to make it appear as an {@link Exp}.
* Only the {@link #evaluate(mondrian.olap.Evaluator)}
Expand Down
4 changes: 4 additions & 0 deletions src/main/mondrian/resource/MondrianResource.xml
Expand Up @@ -352,6 +352,10 @@
<text>Failed to load user-defined function ''{0}'': class ''{1}'' not found</text>
</exception>

<exception id="40195" name="UdfClassMustBePublicAndStatic">
<text>Failed to load user-defined function ''{0}'': class ''{1}'' must be public and static</text>
</exception>

<exception id="40200" name="UdfClassWrongIface">
<text>Failed to load user-defined function ''{0}'': class ''{1}'' does not implement the required interface ''{2}''</text>
</exception>
Expand Down
147 changes: 147 additions & 0 deletions testsrc/main/mondrian/test/UdfTest.java
Expand Up @@ -725,6 +725,78 @@ public void testCachingCurrentDate() {
+ "Row #0: \n");
}

/**
* Test case for a UDF that returns a list.
*
* <p>Test case for bug
* <a href="http://jira.pentaho.com/browse/MONDRIAN-588">MONDRIAN-588,
* "UDF returning List works under 2.4, fails under 3.1.1"</a>.
*/
public void testListUdf() {
TestContext tc = TestContext.create(
null,
null,
null,
null,
"<UserDefinedFunction name=\"Reverse\" className=\""
+ ReverseFunction.class.getName()
+ "\"/>\n",
null);
tc.assertQueryReturns(
"select Reverse([Gender].Members) on 0\n"
+ "from [Sales]",
"Axis #0:\n"
+ "{}\n"
+ "Axis #1:\n"
+ "{[Gender].[All Gender].[M]}\n"
+ "{[Gender].[All Gender].[F]}\n"
+ "{[Gender].[All Gender]}\n"
+ "Row #0: 135,215\n"
+ "Row #0: 131,558\n"
+ "Row #0: 266,773\n");
}

/**
* Tests that a non-static function gives an error.
*/
public void testNonStaticUdfFails() {
TestContext tc = TestContext.create(
null,
null,
null,
null,
"<UserDefinedFunction name=\"Reverse2\" className=\""
+ ReverseFunctionNotStatic.class.getName()
+ "\"/>\n",
null);
tc.assertQueryThrows(
"select Reverse2([Gender].Members) on 0\n"
+ "from [Sales]",
"Failed to load user-defined function 'Reverse2': class "
+ "'mondrian.test.UdfTest$ReverseFunctionNotStatic' must be public "
+ "and static");
}

/**
* Tests a function that takes a member as argument. Want to make sure that
* Mondrian leaves it as a member, does not try to evaluate it to a scalar
* value.
*/
public void testMemberUdfDoesNotEvaluateToScalar() {
TestContext tc = TestContext.create(
null,
null,
null,
null,
"<UserDefinedFunction name=\"MemberName\" className=\""
+ MemberNameFunction.class.getName()
+ "\"/>\n",
null);
tc.assertExprReturns(
"MemberName([Gender].[F])",
"F");
}

// ~ Inner classes --------------------------------------------------------


Expand Down Expand Up @@ -952,6 +1024,81 @@ public String[] getReservedWords() {
return null;
}
}

/**
* Function that reverses a list of members.
*/
public static class ReverseFunction implements UserDefinedFunction {
public Object execute(Evaluator eval, Argument[] args) {
List memberList = (List) args[0].evaluate(eval);
Collections.reverse(memberList);
return memberList;
}

public String getDescription() {
return "Reverses the order of a set";
}

public String getName() {
return "Reverse";
}

public Type[] getParameterTypes() {
return new Type[] {new SetType(MemberType.Unknown)};
}

public String[] getReservedWords() {
return null;
}

public Type getReturnType(Type[] arg0) {
return arg0[0];
}

public Syntax getSyntax() {
return Syntax.Function;
}
}

/**
* Function that is non-static.
*/
public class ReverseFunctionNotStatic extends ReverseFunction {
}

/**
* Function that takes a member and returns a name.
*/
public static class MemberNameFunction implements UserDefinedFunction {
public Object execute(Evaluator eval, Argument[] args) {
Member member = (Member) args[0].evaluate(eval);
return member.getName();
}

public String getDescription() {
return "Returns the name of a member";
}

public String getName() {
return "MemberName";
}

public Type[] getParameterTypes() {
return new Type[] {MemberType.Unknown};
}

public String[] getReservedWords() {
return null;
}

public Type getReturnType(Type[] arg0) {
return new StringType();
}

public Syntax getSyntax() {
return Syntax.Function;
}
}
}

// End UdfTest.java

0 comments on commit 892e49e

Please sign in to comment.