Skip to content

Commit

Permalink
MONDRIAN: Display level name in drill-through (if it's different from…
Browse files Browse the repository at this point in the history
… level key).

[git-p4: depot-paths = "//open/mondrian/": change = 3019]
  • Loading branch information
julianhyde committed Dec 31, 2004
1 parent ad9b70c commit 4d82a79
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 55 deletions.
37 changes: 28 additions & 9 deletions src/main/mondrian/rolap/RolapAggregationManager.java
Expand Up @@ -45,7 +45,11 @@ public abstract class RolapAggregationManager implements CellReader {
* query for levels below each current member. This additional context
* makes the drill-through queries easier for humans to understand.
**/
static CellRequest makeRequest(RolapMember[] members, boolean extendedContext) {
static CellRequest makeRequest(
RolapMember[] members,
boolean extendedContext)
{
boolean showNames = extendedContext;
if (!(members[0] instanceof RolapStoredMeasure)) {
return null;
}
Expand All @@ -56,14 +60,16 @@ static CellRequest makeRequest(RolapMember[] members, boolean extendedContext) {
RolapStar star = starMeasure.table.star;
CellRequest request = new CellRequest(starMeasure);
HashMap mapLevelToColumn = (HashMap)
star.mapCubeToMapLevelToColumn.get(measure.cube);
star.mapCubeToMapLevelToColumn.get(measure.cube);
HashMap mapLevelNameToColumn = (HashMap)
star.mapCubeToMapLevelNameToColumn.get(measure.cube);
for (int i = 1; i < members.length; i++) {
RolapMember member = members[i];
RolapLevel previousLevel = null;
if (extendedContext) {
// Add the key columns as non-constraining columns. For example,
// if they asked for [Gender].[M], [Store].[USA].[CA] then
// the following levels are in play:
// Add the key columns as non-constraining columns. For
// example, if they asked for [Gender].[M], [Store].[USA].[CA]
// then the following levels are in play:
// Gender = 'M'
// Marital Status not constraining
// Nation = 'USA'
Expand All @@ -79,13 +85,21 @@ static CellRequest makeRequest(RolapMember[] members, boolean extendedContext) {
// and [Nation] = 'USA'
// and [State] = 'CA'
//
RolapLevel[] levels = (RolapLevel[]) member.getHierarchy().getLevels();
RolapLevel[] levels = (RolapLevel[])
member.getHierarchy().getLevels();
for (int j = levels.length - 1,
depth = member.getLevel().getDepth(); j > depth; j--) {
final RolapLevel level = levels[j];
RolapStar.Column column = (RolapStar.Column) mapLevelToColumn.get(level);
RolapStar.Column column = (RolapStar.Column)
mapLevelToColumn.get(level);
if (column != null) {
request.addConstrainedColumn(column, null);
if (showNames && level.nameExp != null) {
RolapStar.Column nameColumn = (RolapStar.Column)
mapLevelNameToColumn.get(level);
Util.assertTrue(nameColumn != null);
request.addConstrainedColumn(nameColumn, null);
}
}
}
}
Expand Down Expand Up @@ -136,9 +150,14 @@ static CellRequest makeRequest(RolapMember[] members, boolean extendedContext) {
// (this happens in virtual cubes). The starMeasure only has
// a value for the 'all' member of the hierarchy.
return null;
} else {
request.addConstrainedColumn(column, m.key);
}
request.addConstrainedColumn(column, m.key);
if (showNames && level.nameExp != null) {
RolapStar.Column nameColumn = (RolapStar.Column)
mapLevelNameToColumn.get(level);
Util.assertTrue(nameColumn != null);
request.addConstrainedColumn(nameColumn, null);
}
}
}
return request;
Expand Down
64 changes: 45 additions & 19 deletions src/main/mondrian/rolap/RolapCube.java
Expand Up @@ -372,11 +372,18 @@ void registerDimension(RolapDimension dimension) {
this.fact);
RolapHierarchy[] hierarchies = (RolapHierarchy[])
dimension.getHierarchies();
HashMap mapLevelToColumn = (HashMap) star.mapCubeToMapLevelToColumn.get(this);
HashMap mapLevelToColumn = (HashMap)
star.mapCubeToMapLevelToColumn.get(this);
if (mapLevelToColumn == null) {
mapLevelToColumn = new HashMap();
star.mapCubeToMapLevelToColumn.put(this, mapLevelToColumn);
}
HashMap mapLevelNameToColumn = (HashMap)
star.mapCubeToMapLevelNameToColumn.get(this);
if (mapLevelNameToColumn == null) {
mapLevelNameToColumn = new HashMap();
star.mapCubeToMapLevelNameToColumn.put(this, mapLevelNameToColumn);
}
for (int k = 0; k < hierarchies.length; k++) {
RolapHierarchy hierarchy = hierarchies[k];
HierarchyUsage hierarchyUsage = schema.getUsage(hierarchy,this);
Expand Down Expand Up @@ -410,31 +417,50 @@ void registerDimension(RolapDimension dimension) {
if (level.keyExp == null) {
continue;
} else {
RolapStar.Column column = new RolapStar.Column();
if (level.keyExp instanceof MondrianDef.Column) {
String tableName = ((MondrianDef.Column) level.keyExp).table;
column.table = table.findAncestor(tableName);
if (column.table == null) {
throw Util.newError(
"Level '" + level.getUniqueName() +
"' of cube '" + this +
"' is invalid: table '" + tableName +
"' is not found in current scope");
}
} else {
column.table = table;
}
column.expression = level.keyExp;
column.isNumeric = (level.flags & RolapLevel.NUMERIC) != 0;
RolapStar.Column column = makeColumnForLevelExpr(level,
table, level.keyExp);
column.isNumeric = (level.flags & RolapLevel.NUMERIC) != 0;
table.columns.add(column);
mapLevelToColumn.put(level, column);
star.mapColumnToName.put(column, level.getName());
if (level.nameExp != null) {
final RolapStar.Column nameColumn =
makeColumnForLevelExpr(level, table, level.nameExp);
table.columns.add(nameColumn);
mapLevelNameToColumn.put(level, nameColumn);
star.mapColumnToName.put(nameColumn, level.getName());
star.mapColumnToName.put(column, level.getName() + " (Key)");
} else {
star.mapColumnToName.put(column, level.getName());
}
}
}
}
}

public Member[] getMembersForQuery(String query, List calcMembers) {
private RolapStar.Column makeColumnForLevelExpr(
RolapLevel level,
RolapStar.Table table,
MondrianDef.Expression exp)
{
RolapStar.Column column = new RolapStar.Column();
if (exp instanceof MondrianDef.Column) {
String tableName = ((MondrianDef.Column) exp).table;
column.table = table.findAncestor(tableName);
if (column.table == null) {
throw Util.newError(
"Level '" + level.getUniqueName() +
"' of cube '" + this +
"' is invalid: table '" + tableName +
"' is not found in current scope");
}
} else {
column.table = table;
}
column.expression = exp;
return column;
}

public Member[] getMembersForQuery(String query, List calcMembers) {
throw new UnsupportedOperationException();
}

Expand Down
10 changes: 5 additions & 5 deletions src/main/mondrian/rolap/RolapHierarchy.java
Expand Up @@ -100,7 +100,7 @@ class RolapHierarchy extends HierarchyBase
}
this.levels = new RolapLevel[xmlHierarchy.levels.length + 1];
this.levels[0] = new RolapLevel(
this, 0, "(All)", null, null, null, null,
this, 0, "(All)", null, null, null, null, null,
null, RolapProperty.emptyArray,
RolapLevel.ALL | RolapLevel.UNIQUE,
RolapLevel.HideMemberCondition.Never,
Expand Down Expand Up @@ -186,7 +186,7 @@ void init(RolapCube cube)

RolapLevel newLevel(String name, int flags) {
RolapLevel level = new RolapLevel(
this, this.levels.length, name, null, null,
this, this.levels.length, name, null, null, null,
null, null, null, RolapProperty.emptyArray, flags,
RolapLevel.HideMemberCondition.Never, LevelType.Regular);
this.levels = (RolapLevel[]) RolapUtil.addElement(this.levels, level);
Expand Down Expand Up @@ -439,7 +439,7 @@ synchronized Exp getAggregateChildrenExpression() {
*
* <p>For example, in the demo schema the [HR].[Employee] dimension
* contains a parent-child hierarchy:
*
*
* <pre>
* &lt;Dimension name="Employees" foreignKey="employee_id"&gt;
* &lt;Hierarchy hasAll="true" allMemberName="All Employees"
Expand Down Expand Up @@ -514,7 +514,7 @@ RolapDimension createClosedPeerDimension(
new MondrianDef.Column(clos.table.name, clos.parentColumn);
RolapLevel level = new RolapLevel(peerHier, index++,
"Closure",
keyExp, null,
keyExp, null, null,
null, null, // no longer a parent-child hierarchy
null, RolapProperty.emptyArray, flags,
src.hideMemberCondition, src.getLevelType());
Expand All @@ -530,7 +530,7 @@ RolapDimension createClosedPeerDimension(
keyExp = new MondrianDef.Column(clos.table.name, clos.childColumn);
RolapLevel sublevel = new RolapLevel(peerHier, index++,
"Item",
keyExp, null,
keyExp, null, null,
null, null, // no longer a parent-child hierarchy
null, RolapProperty.emptyArray, flags,
src.hideMemberCondition, src.getLevelType());
Expand Down
27 changes: 22 additions & 5 deletions src/main/mondrian/rolap/RolapLevel.java
Expand Up @@ -41,6 +41,11 @@ class RolapLevel extends LevelBase
RolapProperty[] properties;
RolapProperty[] inheritedProperties;

/**
* Ths expression which gives the name of members of this level. If null,
* members are named using the key expression.
*/
final MondrianDef.Expression nameExp;
/** The expression which joins to the parent member in a parent-child
* hierarchy, or null if this is a regular hierarchy. */
final MondrianDef.Expression parentExp;
Expand All @@ -62,13 +67,19 @@ class RolapLevel extends LevelBase
* @pre levelType != null
* @pre hideMemberCondition != null
*/
RolapLevel(RolapHierarchy hierarchy, int depth, String name,
RolapLevel(RolapHierarchy hierarchy,
int depth,
String name,
MondrianDef.Expression keyExp,
MondrianDef.Expression nameExp,
MondrianDef.Expression ordinalExp,
MondrianDef.Expression parentExp, String nullParentValue,
MondrianDef.Closure xmlClosure, RolapProperty[] properties,
MondrianDef.Expression parentExp,
String nullParentValue,
MondrianDef.Closure xmlClosure,
RolapProperty[] properties,
int flags,
HideMemberCondition hideMemberCondition,
HideMemberCondition
hideMemberCondition,
LevelType levelType)
{
Util.assertPrecondition(properties != null, "properties != null");
Expand All @@ -86,6 +97,12 @@ class RolapLevel extends LevelBase
this.unique = (flags & UNIQUE) == UNIQUE;
this.depth = depth;
this.keyExp = keyExp;
if (nameExp != null) {
if (nameExp instanceof MondrianDef.Column) {
checkColumn((MondrianDef.Column) nameExp);
}
}
this.nameExp = nameExp;
if (ordinalExp != null) {
if (ordinalExp instanceof MondrianDef.Column) {
checkColumn((MondrianDef.Column) ordinalExp);
Expand Down Expand Up @@ -167,7 +184,7 @@ private Property lookupProperty(ArrayList list, String propertyName) {
{
this(
hierarchy, depth, xmlLevel.name, xmlLevel.getKeyExp(),
xmlLevel.getOrdinalExp(),
xmlLevel.getNameExp(), xmlLevel.getOrdinalExp(),
xmlLevel.getParentExp(), xmlLevel.nullParentValue,
xmlLevel.closure, createProperties(xmlLevel),
(xmlLevel.type.equals("Numeric") ? NUMERIC : 0) |
Expand Down
17 changes: 13 additions & 4 deletions src/main/mondrian/rolap/RolapStar.java
Expand Up @@ -44,13 +44,22 @@ public class RolapStar {
DataSource dataSource;
Measure[] measures;
public Table factTable;
/** Maps {@link RolapCube} to a {@link HashMap} which maps
/**
* Maps {@link RolapCube} to a {@link HashMap} which maps
* {@link RolapLevel} to {@link Column}. The double indirection is
* necessary because in different cubes, a shared hierarchy might be joined
* onto the fact table at different levels. */
* onto the fact table at different levels.
*/
final HashMap mapCubeToMapLevelToColumn = new HashMap();
/** Maps {@link Column} to {@link String} for each column which is a key
* to a level. */
/**
* As {@link #mapCubeToMapLevelToColumn}, but holds name columns.
*/
final HashMap mapCubeToMapLevelNameToColumn = new HashMap();

/**
* Maps {@link Column} to {@link String} for each column which is a key
* to a level.
*/
final HashMap mapColumnToName = new HashMap();

/** holds all aggregations of this star */
Expand Down
29 changes: 16 additions & 13 deletions testsrc/main/mondrian/test/ParentChildHierarchyTest.java
Expand Up @@ -384,6 +384,7 @@ public void testParentChildDrillThrough() {
checkDrillThroughSql(
result,
0,
extendedContext,
"[Employees].[All Employees]",
"$39,431.67",
"select" +
Expand All @@ -392,7 +393,7 @@ public void testParentChildDrillThrough() {
"from `time_by_day` as `time_by_day`," +
" `salary` as `salary` " +
"where `salary`.`pay_date` = `time_by_day`.`the_date`" +
" and `time_by_day`.`the_year` = 1997", extendedContext);
" and `time_by_day`.`the_year` = 1997");

// Drill-through for row #1, [Employees].[All].[Sheri Nowmer]
// Note that the SQL does not contain the employee_closure table.
Expand All @@ -401,36 +402,38 @@ public void testParentChildDrillThrough() {
checkDrillThroughSql(
result,
1,
extendedContext,
"[Employees].[All Employees].[Sheri Nowmer]",
"$39,431.67",
"select `time_by_day`.`the_year` as `Year`," +
" `employee`.`employee_id` as `Employee Id`," +
" `employee`.`employee_id` as `Employee Id (Key)`," +
" `salary`.`salary_paid` as `Org Salary` " +
"from `time_by_day` as `time_by_day`," +
" `salary` as `salary`," +
" `employee` as `employee` " +
"where `salary`.`pay_date` = `time_by_day`.`the_date`" +
" and `time_by_day`.`the_year` = 1997" +
" and `salary`.`employee_id` = `employee`.`employee_id`" +
" and `employee`.`employee_id` = 1", extendedContext);
" and `employee`.`employee_id` = 1");

// Drill-through for row #2, [Employees].[All].[Sheri Nowmer].
// Note that the SQL does not contain the employee_closure table.
checkDrillThroughSql(
result,
2,
extendedContext,
"[Employees].[All Employees].[Derrick Whelply]",
"$36,494.07",
"select `time_by_day`.`the_year` as `Year`," +
" `employee`.`employee_id` as `Employee Id`," +
" `employee`.`employee_id` as `Employee Id (Key)`," +
" `salary`.`salary_paid` as `Org Salary` " +
"from `time_by_day` as `time_by_day`," +
" `salary` as `salary`," +
" `employee` as `employee` " +
"where `salary`.`pay_date` = `time_by_day`.`the_date`" +
" and `time_by_day`.`the_year` = 1997" +
" and `salary`.`employee_id` = `employee`.`employee_id`" +
" and `employee`.`employee_id` = 2", extendedContext);
" and `employee`.`employee_id` = 2");
}

public void testParentChildDrillThroughWithContext() {
Expand All @@ -444,6 +447,7 @@ public void testParentChildDrillThroughWithContext() {
checkDrillThroughSql(
result,
2,
extendedContext,
"[Employees].[All Employees].[Derrick Whelply]",
"$36,494.07",
"select" +
Expand All @@ -459,7 +463,8 @@ public void testParentChildDrillThroughWithContext() {
" `employee`.`position_title` as `Position Title`," +
" `employee`.`management_role` as `Management Role`," +
" `department`.`department_id` as `Department Description`," +
" `employee`.`employee_id` as `Employee Id`," +
" `employee`.`employee_id` as `Employee Id (Key)`," +
" `employee`.`full_name` as `Employee Id`," +
" `salary`.`salary_paid` as `Org Salary` " +
"from" +
" `time_by_day` as `time_by_day`," +
Expand Down Expand Up @@ -488,17 +493,15 @@ public void testParentChildDrillThroughWithContext() {
" and `salary`.`employee_id` = `employee`.`employee_id`" +
" and `salary`.`department_id` = `department`.`department_id`" +
" and `salary`.`employee_id` = `employee`.`employee_id`" +
" and `employee`.`employee_id` = 2",
extendedContext);
" and `employee`.`employee_id` = 2" +
" and `salary`.`employee_id` = `employee`.`employee_id`");
}

private void checkDrillThroughSql(
Result result,
private void checkDrillThroughSql(Result result,
int row,
String expectedMember,
boolean extendedContext, String expectedMember,
String expectedCell,
String expectedSql,
boolean extendedContext)
String expectedSql)
{
final Member empMember = result.getAxes()[1].positions[row].members[0];
assertEquals(expectedMember, empMember.getUniqueName());
Expand Down

0 comments on commit 4d82a79

Please sign in to comment.