Skip to content

Commit

Permalink
MONDRIAN: Implement CAST operator;
Browse files Browse the repository at this point in the history
  Fix bug 1501533, "Properties not working with multiple hierarchies".

[git-p4: depot-paths = "//open/mondrian/": change = 7546]
  • Loading branch information
julianhyde committed Sep 4, 2006
1 parent 714294c commit ef37a3d
Show file tree
Hide file tree
Showing 20 changed files with 487 additions and 60 deletions.
10 changes: 7 additions & 3 deletions src/main/mondrian/calc/impl/GenericCalc.java
Expand Up @@ -11,7 +11,6 @@

import mondrian.olap.*;
import mondrian.olap.fun.FunUtil;
import mondrian.calc.impl.AbstractCalc;
import mondrian.calc.*;

import java.util.List;
Expand All @@ -25,8 +24,9 @@
*/
public abstract class GenericCalc
extends AbstractCalc
implements ListCalc, StringCalc, IntegerCalc, DoubleCalc, VoidCalc,
MemberCalc, LevelCalc, HierarchyCalc, DimensionCalc {
implements ListCalc, StringCalc, IntegerCalc, DoubleCalc, BooleanCalc,
VoidCalc, MemberCalc, LevelCalc, HierarchyCalc, DimensionCalc
{

protected GenericCalc(Exp exp) {
super(exp);
Expand All @@ -52,6 +52,10 @@ public double evaluateDouble(Evaluator evaluator) {
number.doubleValue();
}

public boolean evaluateBoolean(Evaluator evaluator) {
return ((Boolean) evaluate(evaluator)).booleanValue();
}

public void evaluateVoid(Evaluator evaluator) {
final Object result = evaluate(evaluator);
assert result == null;
Expand Down
5 changes: 5 additions & 0 deletions src/main/mondrian/olap/Parser.cup
Expand Up @@ -277,6 +277,7 @@ terminal
AS,
AXIS,
CASE,
CAST,
CELL,
DIMENSION,
ELSE,
Expand Down Expand Up @@ -787,6 +788,10 @@ value_expression_primary ::=
RESULT = new UnresolvedFunCall(
i, Syntax.Function, Parser.toExpArray(lis));
:}
| CAST LPAREN expression:e AS identifier:t RPAREN {:
RESULT = new UnresolvedFunCall(
"CAST", Syntax.Cast, new Exp[] {e, Literal.createSymbol(t)});
:}
| LPAREN exp_list:lis RPAREN {:
// Whereas ([Sales],[Time]) and () are tuples, ([Sales]) and (5)
// are just expressions.
Expand Down
1 change: 1 addition & 0 deletions src/main/mondrian/olap/Scanner.java
Expand Up @@ -227,6 +227,7 @@ private void initReswords() {
// initResword(ParserSym.BACK_COLOR ,"BACK_COLOR");
// initResword(ParserSym.BASC ,"BASC");
// initResword(ParserSym.BDESC ,"BDESC");
initResword(ParserSym.CAST ,"CAST"); // mondrian extension
initResword(ParserSym.CASE ,"CASE");
initResword(ParserSym.CELL ,"CELL");
// initResword(ParserSym.CELL_ORDINAL ,"CELL_ORDINAL");
Expand Down
19 changes: 18 additions & 1 deletion src/main/mondrian/olap/Syntax.java
Expand Up @@ -192,6 +192,23 @@ public String getSignature(String name, int returnType, int[] argTypes) {
*/
public static final Syntax Internal = new Syntax("Internal", 8);

/**
* Syntax for a CAST expression <code>CAST(expression AS type)</code>.
*/
public static final Syntax Cast = new Syntax("Cast", 9) {
public void unparse(String fun, Exp[] args, PrintWriter pw) {
pw.print("CAST(");
args[0].unparse(pw);
pw.print(" AS ");
args[1].unparse(pw);
pw.print(")");
}

public String getSignature(String name, int returnType, int[] argTypes) {
return "CAST(<Expression> AS <Type>)";
}
};

/**
* Expression invoked <code>object&#46;&PROPERTY</code>
* (a variant of {@link #Property}).
Expand Down Expand Up @@ -221,7 +238,7 @@ public static Syntax get(int ordinal) {
new Syntax[] {
Function, Property, Method, Infix,
Prefix, Braces, Parentheses, Case,
Internal});
Internal, Cast});

/**
* Converts a call to a function of this syntax into source code.
Expand Down
2 changes: 2 additions & 0 deletions src/main/mondrian/olap/fun/BuiltinFunTable.java
Expand Up @@ -1925,6 +1925,8 @@ public boolean evaluateBoolean(Evaluator evaluator) {
define(NthQuartileFunDef.ThirdQResolver);

define(CalculatedChildFunDef.instance);

define(CastFunDef.Resolver);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/main/mondrian/olap/fun/CaseMatchFunDef.java
Expand Up @@ -125,7 +125,7 @@ public FunDef resolve(
return null;
}

FunDef dummy = new FunDefBase(this, returnType, ExpBase.getTypes(args)) {};
FunDef dummy = createDummyFunDef(this, returnType, args);
return new CaseMatchFunDef(dummy);
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/mondrian/olap/fun/CaseTestFunDef.java
Expand Up @@ -117,7 +117,7 @@ public FunDef resolve(
if (mismatchingArgs != 0) {
return null;
}
FunDef dummy = new FunDefBase(this, returnType, ExpBase.getTypes(args)) {};
FunDef dummy = createDummyFunDef(this, returnType, args);
return new CaseTestFunDef(dummy);
}

Expand Down
149 changes: 149 additions & 0 deletions src/main/mondrian/olap/fun/CastFunDef.java
@@ -0,0 +1,149 @@
/*
// $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) 2006-2006 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 mondrian.olap.type.Type;
import mondrian.resource.MondrianResource;
import mondrian.calc.Calc;
import mondrian.calc.ExpCompiler;
import mondrian.calc.impl.GenericCalc;
import mondrian.mdx.ResolvedFunCall;

/**
* Definition of the <code>CAST</code> MDX operator.
*
* <p><code>CAST</code> is a mondrian-specific extension to MDX, because the MDX
* standard does not define how values are to be converted from one
* type to another. Microsoft Analysis Services, for Resolver, uses the Visual
* Basic functions <code>CStr</code>, <code>CInt</code>, etc.
* The syntax for this operator was inspired by the <code>CAST</code> operator
* in the SQL standard.
*
* <p>Examples:<ul>
* <li><code>CAST(1 + 2 AS STRING)</code></li>
* <li><code>CAST('12.' || '56' AS NUMERIC)</code></li>
* <li><code>CAST('tr' || 'ue' AS BOOLEAN)</code></li>
* </ul>
*
* @author jhyde
* @version $Id$
* @since Sep 3, 2006
*/
public class CastFunDef extends FunDefBase {
static final ResolverBase Resolver = new ResolverImpl();

private CastFunDef(FunDef dummyFunDef) {
super(dummyFunDef);
}

public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) {
final Type targetType = call.getType();
final Exp arg = call.getArg(0);
final Calc calc = compiler.compileScalar(arg, false);
return new GenericCalc(arg) {
public Calc[] getCalcs() {
return new Calc[] {calc};
}

public Object evaluate(Evaluator evaluator) {
return calc.evaluate(evaluator);
}

public String evaluateString(Evaluator evaluator) {
final Object o = evaluate(evaluator);
if (o == null) {
return null;
}
return String.valueOf(o);
}

public int evaluateInteger(Evaluator evaluator) {
final Object o = evaluate(evaluator);
if (o instanceof String) {
return Integer.valueOf((String) o).intValue();
}
if (o instanceof Number) {
return ((Number) o).intValue();
}
throw cannotConvert(o);
}

public double evaluateDouble(Evaluator evaluator) {
final Object o = evaluate(evaluator);
if (o instanceof String) {
return Double.valueOf((String) o).doubleValue();
}
if (o instanceof Number) {
return ((Number) o).doubleValue();
}
throw cannotConvert(o);
}

public boolean evaluateBoolean(Evaluator evaluator) {
final Object o = evaluate(evaluator);
if (o instanceof Boolean) {
return ((Boolean) o).booleanValue();
}
if (o instanceof String) {
return Boolean.valueOf((String) o).booleanValue();
}
throw cannotConvert(o);
}

private RuntimeException cannotConvert(Object o) {
return Util.newInternal(
"cannot convert value '" + o +
"' to targetType '" + targetType +
"'");
}
};
}

/**
* Resolves calls to the CAST operator.
*/
private static class ResolverImpl extends ResolverBase {

public ResolverImpl() {
super("CAST", "CAST(<Expression> AS <Type>)",
"Converts values to another type", Syntax.Cast);
}

public FunDef resolve(
Exp[] args, Validator validator, int[] conversionCount) {
if (args.length != 2) {
return null;
}
if (!(args[1] instanceof Literal)) {
return null;
}
Literal literal = (Literal) args[1];
String typeName = (String) literal.getValue();
int returnCategory;
if (typeName.equalsIgnoreCase("String")) {
returnCategory = Category.String;
} else if (typeName.equalsIgnoreCase("Numeric")) {
returnCategory = Category.Numeric;
} else if (typeName.equalsIgnoreCase("Boolean")) {
returnCategory = Category.Logical;
} else if (typeName.equalsIgnoreCase("Integer")) {
returnCategory = Category.Integer;
} else {
throw MondrianResource.instance().CastInvalidType.ex(typeName);
}
final FunDef dummyFunDef =
createDummyFunDef(this, returnCategory, args);
return new CastFunDef(dummyFunDef);
}
}
}

// End CastFunDef.java
9 changes: 9 additions & 0 deletions src/main/mondrian/olap/fun/FunUtil.java
Expand Up @@ -1571,6 +1571,15 @@ static boolean equalTuple(Member[] members0, Member[] members1) {
return true;
}

static FunDef createDummyFunDef(
Resolver resolver,
int returnCategory,
Exp[] args)
{
final int[] argCategories = ExpBase.getTypes(args);
return new FunDefBase(resolver, returnCategory, argCategories) {};
}

// Inner classes


Expand Down
2 changes: 1 addition & 1 deletion src/main/mondrian/olap/fun/MultiResolver.java
Expand Up @@ -85,7 +85,7 @@ public FunDef resolve(
}
final String signature = signatures[j];
int returnType = decodeReturnCategory(signature);
FunDef dummy = new FunDefBase(this, returnType, parameterTypes) {};
FunDef dummy = createDummyFunDef(this, returnType, args);
return createFunDef(args, dummy);
}
return null;
Expand Down
83 changes: 48 additions & 35 deletions src/main/mondrian/olap/fun/PropertiesFunDef.java
Expand Up @@ -76,46 +76,59 @@ private ResolverImpl() {
public FunDef resolve(
Exp[] args, Validator validator, int[] conversionCount) {
final int[] argTypes = new int[]{Category.Member, Category.String};
final Exp propertyNameExp = args[1];
final Exp memberExp = args[0];
if ((args.length != 2) ||
(args[0].getCategory() != Category.Member) ||
(args[1].getCategory() != Category.String)) {
(memberExp.getCategory() != Category.Member) ||
(propertyNameExp.getCategory() != Category.String)) {
return null;
}
int returnType;
if (args[1] instanceof Literal) {
String propertyName = (String) ((Literal) args[1]).getValue();
Hierarchy hierarchy = args[0].getType().getHierarchy();
Level[] levels = hierarchy.getLevels();
Property property = lookupProperty(
levels[levels.length - 1], propertyName);
if (property == null) {
// we'll likely get a runtime error
returnType = Category.Value;
} else {
switch (property.getType()) {
case Property.TYPE_BOOLEAN:
returnType = Category.Logical;
break;
case Property.TYPE_NUMERIC:
returnType = Category.Numeric;
break;
case Property.TYPE_STRING:
returnType = Category.String;
break;
default:
throw Util.newInternal("Unknown property type "
+ property.getType());
}
}
int returnType = deducePropertyCategory(memberExp, propertyNameExp);
return new PropertiesFunDef(
getName(), getSignature(), getDescription(),getSyntax(),
returnType, argTypes);
}

/**
* Deduces the category of a property. This is possible only if the
* name is a string literal, and the member's hierarchy is unambigous.
* If the type cannot be deduced, returns {@link Category#Value}.
*
* @param memberExp Expression for the member
* @param propertyNameExp Expression for the name of the property
* @return Category of the property
*/
private int deducePropertyCategory(
Exp memberExp,
Exp propertyNameExp)
{
if (!(propertyNameExp instanceof Literal)) {
return Category.Value;
}
String propertyName = (String) ((Literal) propertyNameExp).getValue();
Hierarchy hierarchy = memberExp.getType().getHierarchy();
if (hierarchy == null) {
return Category.Value;
}
Level[] levels = hierarchy.getLevels();
Property property = lookupProperty(
levels[levels.length - 1], propertyName);
if (property == null) {
// we'll likely get a runtime error
return Category.Value;
} else {
returnType = Category.Value;
switch (property.getType()) {
case Property.TYPE_BOOLEAN:
return Category.Logical;
case Property.TYPE_NUMERIC:
return Category.Numeric;
case Property.TYPE_STRING:
return Category.String;
default:
throw Util.newInternal("Unknown property type "
+ property.getType());
}
}
return new PropertiesFunDef(getName(),
getSignature(),
getDescription(),
getSyntax(),
returnType,
argTypes);
}

public boolean requiresExpression(int k) {
Expand Down
2 changes: 1 addition & 1 deletion src/main/mondrian/olap/fun/SetItemFunDef.java
Expand Up @@ -67,7 +67,7 @@ public FunDef resolve(
throw Util.newError("Argument count does not match set's cardinality " + arity);
}
final int category = arity == 1 ? Category.Member : Category.Tuple;
FunDef dummy = new FunDefBase(this, category, ExpBase.getTypes(args)) {};
FunDef dummy = createDummyFunDef(this, category, args);
return new SetItemFunDef(dummy);
}
};
Expand Down

0 comments on commit ef37a3d

Please sign in to comment.