Skip to content

Commit

Permalink
MONDRIAN: Support for user-defined functions (UDFs).
Browse files Browse the repository at this point in the history
Refactor the function sub-system to make this possible.

[git-p4: depot-paths = "//open/mondrian/": change = 3511]
  • Loading branch information
julianhyde committed Apr 30, 2005
1 parent 3803147 commit face401
Show file tree
Hide file tree
Showing 25 changed files with 682 additions and 633 deletions.
3 changes: 2 additions & 1 deletion src/main/mondrian/olap/ConnectionBase.java
Expand Up @@ -51,7 +51,8 @@ public Query parseQuery(String s) {
try {
boolean debug = false;
Parser parser = new Parser();
Query q = parser.parseInternal(this, s, debug);
final FunTable funTable = getSchema().getFunTable();
Query q = parser.parseInternal(this, s, debug, funTable);
return q;
} catch (Throwable e) {
throw MondrianResource.instance().newFailedToParseQuery(s, e);
Expand Down
7 changes: 7 additions & 0 deletions src/main/mondrian/olap/Evaluator.java
Expand Up @@ -115,6 +115,13 @@ public interface Evaluator {
* allows expressions to modify non empty flag to evaluate their children.
*/
void setNonEmpty(boolean nonEmpty);

/**
* Creates an exception which indicates that an error has occurred during
* the runtime evaluation of a function. The caller should then throw that
* exception.
*/
RuntimeException newEvalException(Object context, String s);
}

// End Evaluator.java
82 changes: 29 additions & 53 deletions src/main/mondrian/olap/FunTable.java
Expand Up @@ -10,89 +10,65 @@
// jhyde, 3 March, 2002
*/
package mondrian.olap;
import mondrian.olap.fun.BuiltinFunTable;

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

/**
* A <code>FunTable</code> resolves a function call, using a particular syntax
* (see {@link FunDef#getSyntax}) and set of arguments to a function
* definition (<code>class {@link FunDef}</code>).
* List of all MDX functions.
*
* <p> It is a singleton class. The default implementation is {@link
* mondrian.olap.fun.BuiltinFunTable}.
**/
public abstract class FunTable {

/** Returns (creating if necessary) the singleton. **/
public static FunTable instance() {
if (instance == null) {
instance = new BuiltinFunTable();
}
return instance;
}
/** the singleton **/
private static FunTable instance;


/** used during initialization **/
protected final List resolvers = new ArrayList();
protected final List funInfoList = new ArrayList();


protected FunTable() {
}

* A function table can resolve a function call, using a particular
* {@link Syntax} and set of arguments, to a
* function definition ({@link FunDef}).
*/
public interface FunTable {
/**
* Resolves a function call to a particular function. If the function is
* overloaded, returns as precise a match to the argument types as
* possible.
**/
public abstract FunDef getDef(FunCall call, Validator resolver);
FunDef getDef(FunCall call, Validator resolver);

/**
* Adds a casting function, if necessary, to ensure that an expression is
* of a given type. Throws an error if conversion is not possible.
*/
public abstract Exp convert(Exp fromExp, int to, Validator resolver);

/**
* This method is called from the constructor, to define the set of
* functions recognized. Derived class can override this method to add more
* functions. Each function is declared by calling {@link #define}.
**/
protected void defineFunctions() {
}

protected abstract void define(FunDef funDef);
Exp convert(Exp fromExp, int to, Validator resolver);

/**
* Returns whether a string is a reserved word.
*/
public abstract boolean isReserved(String s);
boolean isReserved(String s);

/**
* Returns whether a string is a property-style (postfix)
* operator. This is used during parsing to disambiguate
* functions from unquoted member names.
*/
public abstract boolean isProperty(String s);
boolean isProperty(String s);

/**
* Returns whether the <code>k</code>th argument to a function call
* has to be an expression.
*/
public abstract boolean requiresExpression(
FunCall funCall,
int k,
Validator resolver);
boolean requiresExpression(
FunCall funCall,
int k,
Validator resolver);

public List getFunInfoList() {
return Collections.unmodifiableList(this.funInfoList);
}
/**
* Returns a list of words ({@link String}) which may not be used as
* identifiers.
*/
List getReservedWords();

/**
* Returns a list of {@link mondrian.olap.fun.Resolver} objects.
*/
List getResolvers();

/**
* Returns a list of {@link mondrian.olap.fun.FunInfo} objects.
*/
List getFunInfoList();
}

// End FunTable.java
2 changes: 1 addition & 1 deletion src/main/mondrian/olap/Id.java
Expand Up @@ -82,7 +82,7 @@ public void append(String s) {
public Exp accept(Validator validator) {
if (names.length == 1) {
final String s = names[0];
if (FunTable.instance().isReserved(s)) {
if (validator.getFunTable().isReserved(s)) {
return Literal.createSymbol(s.toUpperCase());
}
}
Expand Down
22 changes: 22 additions & 0 deletions src/main/mondrian/olap/Mondrian.xml
Expand Up @@ -42,6 +42,7 @@ Revision is $Id$
<Array name="cubes" type="Cube"/>
<Array name="virtualCubes" type="VirtualCube"/>
<Array name="roles" type="Role"/>
<Array name="userDefinedFunctions" type="UserDefinedFunction"/>
<Code><![CDATA[
Cube getCube(String cubeName) {
for (int i = 0; i < cubes.length; i++) {
Expand Down Expand Up @@ -1029,6 +1030,27 @@ Revision is $Id$
</Attribute>
</Element>

<!-- UserDefinedFunction ====================================================== -->
<Element type="UserDefinedFunction">
<Doc>
A <code>UserDefinedFunction</code> is a function which
extends the MDX language. It must be implemented by a Java
class which implements the interface
<a href="http://mondrian.sourceforge.net/api/mondrian/spi/UserDefinedFunction.html">mondrian.spi.UserDefinedFunction</a>.
</Doc>
<Attribute name="name" required="true">
<Doc>Name with which the user-defined function will be referenced in MDX expressions.</Doc>
</Attribute>
<Attribute name="className" required="true">
<Doc>
Name of the class which implemenets this user-defined function.
Must implement the <code>mondrian.spi.UserDefinedFunction</code>
interface.
</Doc>
</Attribute>
</Element>


</Model>

<!-- End Mondrian.xml -->
11 changes: 11 additions & 0 deletions src/main/mondrian/olap/MondrianResource.xml
Expand Up @@ -376,6 +376,17 @@
<text>Foreign key ''{0}'' of hierarchy ''{1}'' in cube ''{2}'' is not a column in the fact table.</text>
</exception>

<exception id="40190" name="UdfClassNotFound">
<text>Failed to load user-defined function ''{0}'': class ''{1}'' not found</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>

<exception id="40210" name="UdfDuplicateName">
<text>Duplicate user-defined function ''{0}''</text>
</exception>

<!-- ====================================================================== -->
<!-- Loader -->
Expand Down
31 changes: 21 additions & 10 deletions src/main/mondrian/olap/Parser.cup
Expand Up @@ -21,19 +21,25 @@ import java.util.ArrayList;
parser code {:
// Generated from $Id$
private Scanner scanner;
private String sQuery;
private String queryString;
private Connection mdxConnection;
private FunTable funTable;

/** Called only by {@link ConnectionBase#parseQuery} and
/**
* Called only by {@link ConnectionBase#parseQuery} and
* {@link ConnectionBase#parseExpression}.
**/
*/
Query parseInternal(
Connection mdxConnection, String sQuery, boolean debug)
Connection mdxConnection,
String queryString,
boolean debug,
FunTable funTable)
{
Symbol parse_tree = null;
this.scanner = new StringScanner(sQuery, debug);
this.scanner = new StringScanner(queryString, debug);
this.mdxConnection = mdxConnection;
this.sQuery = sQuery;
this.queryString = queryString;
this.funTable = funTable;
try {
if (debug) {
parse_tree = debug_parse();
Expand All @@ -43,11 +49,12 @@ parser code {:
return (Query) parse_tree.value;
} catch (Exception e) {
// "Error while parsing MDX statement '%1'"
throw Util.getRes().newWhileParsingMdx(sQuery, e);
throw Util.getRes().newWhileParsingMdx(queryString, e);
} finally {
this.scanner = null;
this.mdxConnection = null;
this.sQuery = null;
this.queryString = null;
this.funTable = null;
}
}
/** override this function to make your kind of query */
Expand Down Expand Up @@ -107,10 +114,14 @@ parser code {:
}

/**
* Tell if the given string can possibly be an operator with property syntax.
* Returns whether the given identifier can possibly the name of an operator
* with property syntax.
*
* <p>For example, <code>isFunCall("ORDINAL")</code>
* returns true because there is a "&lt;Level&gt;.Ordinal" property.</p>
*/
protected boolean isFunCall(String s) {
return FunTable.instance().isProperty(s);
return funTable.isProperty(s);
}
:};
init with {:
Expand Down
8 changes: 7 additions & 1 deletion src/main/mondrian/olap/Query.java
Expand Up @@ -143,7 +143,7 @@ public void addFormula(String[] names,


public Validator createValidator() {
return new StackValidator(BuiltinFunTable.instance());
return new StackValidator(connection.getSchema().getFunTable());
}

public Object clone() throws CloneNotSupportedException {
Expand Down Expand Up @@ -810,7 +810,13 @@ private class StackValidator implements Validator {
private boolean haveCollectedParameters;
private java.util.Set resolvedNodes = new HashSet();

/**
* Creates a StackValidator.
*
* @pre funTable != null
*/
public StackValidator(FunTable funTable) {
Util.assertPrecondition(funTable != null, "funTable != null");
this.funTable = funTable;
}

Expand Down
5 changes: 5 additions & 0 deletions src/main/mondrian/olap/Schema.java
Expand Up @@ -60,6 +60,11 @@ public interface Schema {
* <code>null</code> if no such role exists.
*/
Role lookupRole(String role);

/**
* Returns this schema's function table.
*/
FunTable getFunTable();
}

// End Schema.java
2 changes: 1 addition & 1 deletion src/main/mondrian/olap/Util.java
Expand Up @@ -968,7 +968,7 @@ public static int hashArray(int h, Object [] a) {
* Creates a very simple implementation of {@link Validator}. (Only
* useful for resolving trivial expressions.)
*/
public static Validator createSimpleResolver(final FunTable funTable) {
public static Validator createSimpleValidator(final FunTable funTable) {
return new Validator() {
public Query getQuery() {
throw new UnsupportedOperationException();
Expand Down

0 comments on commit face401

Please sign in to comment.