Skip to content

Commit

Permalink
MONDRIAN: Allow SSAS2005-compatible syntax for resolving names of dim…
Browse files Browse the repository at this point in the history
…ensions,

hierarchies, levels, members, and in particular multiple hierarchies in a
dimension.
If SsasCompatibleNaming=false (default):
* [Time.Weekly] is preferred by the parser, but now [Time].[Weekly] is accepted;
* Typical unique names are [Time] (dimension), [Time.Weekly] (hierarchy with
different name to dimension), [Store] (hierarchy with same name as its
dimension), [Gender].[Gender] (level).
If SsasCompatibleNaming=true:
* [Time].[Weekly] is preferred by the parser, but [Time.Weekly] is accepted;
* Typical unique names are [Time] (dimension), [Time].[Weekly] (hierarchy
with different name to dimension), [Store].[Store] (hierarchy with same
name as its dimension), [Gender].[Gender].[Gender] (level).
* The parser can find unqualified hierarchies and levels (e.g. the level
[Store City]) and similar things (e.g. [Time].[Weekly].[Week] can be
abbreviated to [Time].[Week])
* If dimension, hierarchy and level have the same name X, to get the level,
you have to write [X].[X].[X].
* If a dimension contains only one hierarchy, the hierarchy can be omitted.
Therefore the behavior tends to be the same as before.
* Axes can appear out of usual columns, rows order (e.g. select x on rows,
y on columns from z).
The level name resolution change is the biggest concern to enabling
SsasCompatibleNaming, because it might break apps. For example, many of our
tests have written [Gender].[Gender].Members, intending to get the two
members of the [Gender] level, and now return the 3 members of the [Gender]
hierarchy.
The test suite runs successfully for both values of SsasCompatibleNaming. I
added methods upgradeQuery and upgradeExpected to massage the differences
between the two behaviors.
Also, unbounded numbers of axes, e.g. 'select expr on 0, expr on 1, ... expr
on 512', per SSAS2005. Previously we topped out at 6.

[git-p4: depot-paths = "//open/mondrian/": change = 12395]
  • Loading branch information
julianhyde committed Feb 23, 2009
1 parent 978aaed commit a5aaff6
Show file tree
Hide file tree
Showing 103 changed files with 4,316 additions and 1,705 deletions.
3 changes: 1 addition & 2 deletions demo/FoodMart.xml
Expand Up @@ -56,8 +56,7 @@
<Level name="Month" column="month_of_year" uniqueMembers="false" type="Numeric"
levelType="TimeMonths"/>
</Hierarchy>
<Hierarchy hasAll="true" name="Weekly" primaryKey="time_id"
defaultMember="[Time.Weekly].[All Time.Weeklys].[1997]">
<Hierarchy hasAll="true" name="Weekly" primaryKey="time_id">
<Table name="time_by_day"/>
<Level name="Year" column="the_year" type="Numeric" uniqueMembers="true"
levelType="TimeYears"/>
Expand Down
89 changes: 79 additions & 10 deletions src/main/mondrian/calc/impl/AbstractExpCompiler.java
Expand Up @@ -148,8 +148,8 @@ public MemberCalc compileMember(Exp exp) {
final Type type = exp.getType();
if (type instanceof DimensionType) {
final DimensionCalc dimensionCalc = compileDimension(exp);
return new DimensionCurrentMemberFunDef.CalcImpl(
new DummyExp(TypeUtil.toMemberType(type)), dimensionCalc);
return new DimensionCurrentMemberCalc(
new DummyExp(TypeUtil.toMemberType(type)), dimensionCalc);
} else if (type instanceof HierarchyType) {
final HierarchyCalc hierarchyCalc = compileHierarchy(exp);
return new HierarchyCurrentMemberFunDef.CalcImpl(
Expand Down Expand Up @@ -188,20 +188,36 @@ public DimensionCalc compileDimension(Exp exp) {

public HierarchyCalc compileHierarchy(Exp exp) {
final Type type = exp.getType();
if (type instanceof DimensionType ||
type instanceof MemberType) {
// <Dimension> --> <Dimension>.CurrentMember.Hierarchy
if (type instanceof DimensionType) {
// <Dimension> --> unique Hierarchy else error
// Resolve at compile time if constant
final Dimension dimension = type.getDimension();
if (dimension != null) {
final Hierarchy hierarchy =
FunUtil.getDimensionDefaultHierarchy(dimension);
if (hierarchy != null) {
return (HierarchyCalc) ConstantCalc.constantHierarchy(
hierarchy);
}
}
final DimensionCalc dimensionCalc = compileDimension(exp);
return new DimensionHierarchyCalc(
new DummyExp(HierarchyType.forType(type)),
dimensionCalc);
}
if (type instanceof MemberType) {
// <Member> --> <Member>.Hierarchy
final MemberCalc memberCalc = compileMember(exp);
return new MemberHierarchyFunDef.CalcImpl(
new DummyExp(HierarchyType.forType(type)),
memberCalc);
new DummyExp(HierarchyType.forType(type)),
memberCalc);
}
if (type instanceof LevelType) {
// <Level> --> <Level>.Hierarchy
final LevelCalc levelCalc = compileLevel(exp);
return new LevelHierarchyFunDef.CalcImpl(
new DummyExp(HierarchyType.forType(type)),
levelCalc);
new DummyExp(HierarchyType.forType(type)),
levelCalc);
}
assert type instanceof HierarchyType;
return (HierarchyCalc) compile(exp);
Expand Down Expand Up @@ -383,7 +399,7 @@ public Calc compileScalar(Exp exp, boolean specific) {
final DimensionCalc dimensionCalc = compileDimension(exp);
MemberType memberType = MemberType.forType(type);
final MemberCalc dimensionCurrentMemberCalc =
new DimensionCurrentMemberFunDef.CalcImpl(
new DimensionCurrentMemberCalc(
new DummyExp(memberType),
dimensionCalc);
return new MemberValueCalc(
Expand Down Expand Up @@ -546,6 +562,59 @@ public Iterable<Member[]> evaluateTupleIterable(Evaluator evaluator) {
return tupleListCalc.evaluateTupleList(evaluator);
}
}

/**
* Computes the hierarchy of a dimension
*/
private static class DimensionHierarchyCalc extends AbstractHierarchyCalc {
private final DimensionCalc dimensionCalc;

protected DimensionHierarchyCalc(Exp exp, DimensionCalc dimensionCalc) {
super(exp, new Calc[] {dimensionCalc});
this.dimensionCalc = dimensionCalc;
}

public Hierarchy evaluateHierarchy(Evaluator evaluator) {
Dimension dimension =
dimensionCalc.evaluateDimension(evaluator);
final Hierarchy hierarchy =
FunUtil.getDimensionDefaultHierarchy(dimension);
if (hierarchy != null) {
return hierarchy;
}
throw FunUtil.newEvalException(
MondrianResource.instance()
.CannotImplicitlyConvertDimensionToHierarchy
.ex(
dimension.getName()));
}
}

/**
* Computation that returns the current member of a dimension.
*/
public static class DimensionCurrentMemberCalc extends AbstractMemberCalc {
private final DimensionCalc dimensionCalc;

public DimensionCurrentMemberCalc(Exp exp, DimensionCalc dimensionCalc) {
super(exp, new Calc[] {dimensionCalc});
this.dimensionCalc = dimensionCalc;
}

protected String getName() {
return "CurrentMember";
}

public Member evaluateMember(Evaluator evaluator) {
Dimension dimension =
dimensionCalc.evaluateDimension(evaluator);
return evaluator.getContext(dimension);
}

public boolean dependsOn(Dimension dimension) {
return dimensionCalc.getType().usesDimension(dimension, true) ;
}
}
}

// End AbstractExpCompiler.java
101 changes: 74 additions & 27 deletions src/main/mondrian/olap/AxisOrdinal.java
Expand Up @@ -18,42 +18,89 @@
* @since Feb 21, 2003
* @version $Id$
*/
public enum AxisOrdinal {
public interface AxisOrdinal {
/**
* Returns the name of this axis, e.g. "COLUMNS", "SLICER", "AXIS(17)".
*
* @return Name of the axis
*/
String name();

/** No axis.*/
NONE,
/**
* Returns the ordinal of this axis.
* {@link StandardAxisOrdinal#COLUMNS} = 0,
* {@link StandardAxisOrdinal#ROWS} = 1, etc.
*
* @return ordinal of this axis
*/
int logicalOrdinal();

/** Slicer axis. */
SLICER,
/**
* Returns whether this is the filter (slicer) axis.
*
* @return whether this is the filter axis
*/
boolean isFilter();

/** Columns axis (also known as X axis), logical ordinal = 0. */
COLUMNS,
public enum StandardAxisOrdinal implements AxisOrdinal {
/** No axis.*/
NONE,

/** Rows axis (also known as Y axis), logical ordinal = 1. */
ROWS,
/** Slicer axis. */
SLICER,

/** Pages axis, logical ordinal = 2. */
PAGES,
/** Columns axis (also known as X axis), logical ordinal = 0. */
COLUMNS,

/** Chapters axis, logical ordinal = 3. */
CHAPTERS,
/** Rows axis (also known as Y axis), logical ordinal = 1. */
ROWS,

/** Sections axis, logical ordinal = 4. */
SECTIONS;
/** Pages axis, logical ordinal = 2. */
PAGES,

public static AxisOrdinal forLogicalOrdinal(int ordinal) {
return values()[ordinal + 2];
}
/** Chapters axis, logical ordinal = 3. */
CHAPTERS,

/**
* Returns the ordinal of this axis with {@link #COLUMNS} = 0,
* {@link #ROWS} = 1, etc.
*/
public int logicalOrdinal() {
return ordinal() - 2;
}
/** Sections axis, logical ordinal = 4. */
SECTIONS;

/**
* Returns an axis with a given number.
*
* <p>If ordinal is greater than 4, returns a non-standard axis called
* "AXIS(n)". Never returns null.
*
* @param ordinal Ordinal
* @return Axis
*/
public static AxisOrdinal forLogicalOrdinal(final int ordinal) {
if (ordinal + 2 > SECTIONS.ordinal()) {
return new AxisOrdinal() {
public String name() {
return "AXIS(" + ordinal + ")";
}

public int logicalOrdinal() {
return ordinal;
}

public static final int MaxLogicalOrdinal = SECTIONS.logicalOrdinal() + 1;
public boolean isFilter() {
return false;
}
};
} else {
return values()[ordinal + 2];
}
}

public int logicalOrdinal() {
return ordinal() - 2;
}

public boolean isFilter() {
return this == SLICER;
}
}
}

// End AxisOrdinal.java
// End AxisOrdinal.java
56 changes: 44 additions & 12 deletions src/main/mondrian/olap/CubeBase.java
Expand Up @@ -15,6 +15,8 @@

import mondrian.resource.MondrianResource;

import java.util.List;

/**
* <code>CubeBase</code> is an abstract implementation of {@link Cube}.
*
Expand Down Expand Up @@ -98,25 +100,55 @@ public Hierarchy lookupHierarchy(Id.Segment s, boolean unique) {
return null;
}

public OlapElement lookupChild(SchemaReader schemaReader, Id.Segment s)
{
return lookupChild(schemaReader, s, MatchType.EXACT);
}

public OlapElement lookupChild(
SchemaReader schemaReader, Id.Segment s, MatchType matchType)
SchemaReader schemaReader,
Id.Segment s,
MatchType matchType)
{
Dimension mdxDimension = (Dimension)lookupDimension(s);
if (mdxDimension != null ||
MondrianProperties.instance().NeedDimensionPrefix.get()) {
Dimension mdxDimension = lookupDimension(s);
if (mdxDimension != null) {
return mdxDimension;
}

//maybe this is not a dimension - maybe it's hierarchy, level or name
final List<Dimension> dimensions = schemaReader.getCubeDimensions(this);

// Look for hierarchies named '[dimension.hierarchy]'.
if (MondrianProperties.instance().SsasCompatibleNaming.get()
&& s.name.contains(".")) {
for (Dimension dimension : dimensions) {
if (!s.name.startsWith(dimension.getName())) {
// Rough check to save time.
continue;
}
for (Hierarchy hierarchy :
schemaReader.getDimensionHierarchies(dimension))
{
if (Util.equalName(
s.name,
dimension.getName()
+ "."
+ hierarchy.getName()))
{
return hierarchy;
}
}
}
}

// Try hierarchies, levels and members.
for (Dimension dimension : dimensions) {
OlapElement mdxElement = dimension.lookupChild(
schemaReader, s, matchType);
if (mdxElement != null) {
if (mdxElement instanceof Member
&& MondrianProperties.instance().NeedDimensionPrefix.get())
{
// With this property setting, don't allow members to be
// referenced without at least a dimension prefix. We
// allow [Store].[USA].[CA] or even [Store].[CA] but not
// [USA].[CA].
continue;
}
return mdxElement;
}
}
Expand All @@ -134,9 +166,9 @@ public Dimension getTimeDimension() {
return null;
}

public OlapElement lookupDimension(Id.Segment s) {
public Dimension lookupDimension(Id.Segment s) {
for (Dimension dimension : dimensions) {
if (dimension.getName().equalsIgnoreCase(s.name)) {
if (Util.equalName(dimension.getName(), s.name)) {
return dimension;
}
}
Expand Down
14 changes: 9 additions & 5 deletions src/main/mondrian/olap/DelegatingSchemaReader.java
Expand Up @@ -44,6 +44,14 @@ public Role getRole() {
return schemaReader.getRole();
}

public List<Dimension> getCubeDimensions(Cube cube) {
return schemaReader.getCubeDimensions(cube);
}

public List<Hierarchy> getDimensionHierarchies(Dimension dimension) {
return schemaReader.getDimensionHierarchies(dimension);
}

public List<Member> getHierarchyRootMembers(Hierarchy hierarchy) {
return schemaReader.getHierarchyRootMembers(hierarchy);
}
Expand Down Expand Up @@ -98,7 +106,7 @@ public OlapElement lookupCompound(
boolean failIfNotFound, int category, MatchType matchType)
{
return schemaReader.lookupCompound(
parent, names, failIfNotFound, category, matchType);
parent, names, failIfNotFound, category, matchType);
}

public Member getCalculatedMember(List<Id.Segment> nameParts) {
Expand Down Expand Up @@ -194,10 +202,6 @@ public List<Member> getMemberChildren(List<Member> members, Evaluator context) {
return schemaReader.getMemberChildren(members, context);
}

public Member lookupMemberChildByName(Member member,Id.Segment memberName) {
return lookupMemberChildByName(member, memberName, MatchType.EXACT);
}

public Member lookupMemberChildByName(
Member member, Id.Segment memberName, MatchType matchType)
{
Expand Down

0 comments on commit a5aaff6

Please sign in to comment.