Skip to content

Commit

Permalink
Add canDrillThrough() method to interface mondrian.olap.Cell, impleme…
Browse files Browse the repository at this point in the history
…nted in RolapCell. Changed getDrillThroughSQL() to getDrillThroughSQL(boolean b), extends the context of the returned sql query, adds column names to query.

[git-p4: depot-paths = "//open/mondrian/": change = 663]
  • Loading branch information
robinchiedo committed Jul 22, 2003
1 parent 5a7b487 commit 866314d
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 16 deletions.
20 changes: 17 additions & 3 deletions src/main/mondrian/olap/Cell.java
Expand Up @@ -57,10 +57,24 @@ public interface Cell {
boolean isError();

/**
* Returns a SQL query which calculates the value of this cell. The result
* is null if the cell is based upon a calculated member.
* Returns a SQL query that, when executed, returns drill through data
* for this Cell.
* If the parameter extendedContext is true, then the
* query will include all the levels (i.e. columns) of non-constraining members
* (i.e. members which are at the "All" level).
* If the parameter extendedContext is false, the query will exclude
* the levels (coulmns) of non-constraining members.
* The result is null if the cell is based upon a calculated member.
*
*/
String getDrillThroughSQL();
String getDrillThroughSQL(boolean extendedContext);

/**
* Returns true if drill through is possible for this Cell.
* Returns false if the Cell is based on a calculated measure.
* @return true if can drill through on this cell
*/
boolean canDrillThrough();

/**
* Returns the value of a property.
Expand Down
82 changes: 82 additions & 0 deletions src/main/mondrian/rolap/RolapAggregationManager.java
Expand Up @@ -126,6 +126,88 @@ CellRequest makeRequest(RolapMember[] members) {
}
return request;
}

/**
* Creates a request to evaluate the cell identified by
* <code>members</code>.
* If any of the members is an "All" member, then it exapnds that member
* adding all of the levels of that member to the request.
* If any of the members is the null member, returns
* null, since there is no cell. If the measure is calculated, returns null.
* @param members Array of RolapMembers that identify this cell.
* @return a CellRequest object for the cell
*/
CellRequest makeDrillThroughRequest(
RolapMember[] members) {
if (!(members[0] instanceof RolapStoredMeasure)) {
return null;
}
RolapStoredMeasure measure = (RolapStoredMeasure) members[0];
final RolapStar.Measure starMeasure = (RolapStar.Measure)
measure.starMeasure;
Util.assertTrue(starMeasure != null);
RolapStar star = starMeasure.table.star;
CellRequest request = new CellRequest(starMeasure);
HashMap mapLevelToColumn = (HashMap) star.mapCubeToMapLevelToColumn.get(measure.cube);
for (int i = 1; i < members.length; i++) {

// If this is an All member, then expand children, and add all columns as non-constraining
if ( members[i].isAll() ) {
RolapLevel rl = (RolapLevel) members[i].getLevel();
while ( (rl = (RolapLevel) rl.getChildLevel()) != null ) {
RolapStar.Column column = (RolapStar.Column) mapLevelToColumn.get(rl);
if (column == null) {
// This hierarchy is not one which qualifies the starMeasure (this happens in
// virtual cubes). The starMeasure only has a value for the 'all' member of
// the hierarchy (which this is not).
return null;
}
else {
// add the column as a non-constraining column, by using a null value
request.addConstrainedColumn(column, null);
}
}
}
// else its not an All member, so add this member plus parent member columns as constraining
else {
RolapLevel previousLevel = null;
for (RolapMember m = members[i];
m != null;
m = (RolapMember) m.getParentMember()) {
if (m.key == null) {
if (m == m.getHierarchy().getNullMember()) {
// cannot form a request if one of the members is null
return null;
} else if (m.isAll()) {
continue;
} else {
throw Util.getRes().newInternal("why is key null?");
}
}
RolapLevel level = (RolapLevel) m.getLevel();
if (level == previousLevel) {
// We are looking at a parent in a parent-child hierarchy,
// for example, we have moved from Fred to Fred's boss,
// Wilma. We don't want to include Wilma's key in the
// request.
continue;
}
previousLevel = level;
RolapStar.Column column =
(RolapStar.Column) mapLevelToColumn.get(level);
if (column == null) {
// This hierarchy is not one which qualifies the starMeasure (this happens in
// virtual cubes). The starMeasure only has a value for the 'all' member of
// the hierarchy (which this is not).
return null;
} else {
request.addConstrainedColumn(column, m.key);
}
}
}
}
return request;
}

/**
* Returns the value of a cell from an existing aggregation.
Expand Down
93 changes: 89 additions & 4 deletions src/main/mondrian/rolap/RolapResult.java
Expand Up @@ -17,6 +17,8 @@
import mondrian.rolap.agg.CellRequest;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.io.PrintWriter;

/**
Expand Down Expand Up @@ -484,15 +486,98 @@ public boolean isNull() {
public boolean isError() {
return value instanceof Throwable;
}
public String getDrillThroughSQL() {
RolapEvaluator evaluator = getEvaluator();
final CellRequest cellRequest = AggregationManager.instance().makeRequest(evaluator.currentMembers);

/**
* Create an sql query that, when executed, will return the drill through
* data for this cell. If the parameter extendedContext is true, then the
* query will include all the levels (i.e. columns) of non-constraining members
* (i.e. members which are at the "All" level).
* If the parameter extendedContext is false, the query will exclude
* the levels (coulmns) of non-constraining members.
*/
public String getDrillThroughSQL(boolean extendedContext) {
final RolapEvaluator evaluator = getEvaluator();
final RolapMember[] currentMembers = evaluator.currentMembers;
// create a list of column names
List columnList = new ArrayList();
// loop on each member to discover names
for (int i = 1; i < currentMembers.length; i++) {
if ( currentMembers[i].isAll() ) {
if ( extendedContext ) {
// if extendedContext, then also add "All" members
// so we add all level names to list of column names
RolapLevel rl = (RolapLevel) currentMembers[i].getLevel();
while ( (rl = (RolapLevel) rl.getChildLevel()) != null ) {
// add name of level to list
columnList.add(rl.getName());
}
}
}
else {
// its not an All member...
// add level names in child-to-parent order same as MakeRequest
for (RolapMember m = currentMembers[i]; m != null;
m = (RolapMember) m.getParentMember()) {
if (!m.isAll()) {
columnList.add(m.getLevel().getName());
}
}
}
}
// add measure name to column list names
columnList.add(currentMembers[0].getName());

CellRequest cellRequest;
if ( extendedContext ) {
cellRequest = AggregationManager.instance().makeDrillThroughRequest(currentMembers);
}
else {
cellRequest = AggregationManager.instance().makeRequest(currentMembers);
}
if (cellRequest == null) {
return null;
} else {
return AggregationManager.instance().getDrillThroughSQL(cellRequest);
// get sql query
String sql = AggregationManager.instance().getDrillThroughSQL(cellRequest);
// replace c0 c1 etc. with column names
// A problem with matching for c0 c1 is that someone might have a column named abc1
// all columns are quoted, so we have to include quotes in the match, which means using the right quote for the right database
// but at this point there is no connection to discover the quote char, so, use a regular expression character class
// possible chars are " ' ` [ or nothing.
// Match a word like c0 quoted with any of possible quote chars, save as group
Pattern re = Pattern.compile("(\\b[\\\"\\'\\`\\[]?c[0-9]+[\\\"\\'\\`\\]]?\\b)");
Matcher pm = re.matcher(sql);

for ( int i = 0; i < columnList.size(); i++ ) {
if ( ! pm.find() ) {
// a not successful find should not happen
continue;
}
String s = pm.group(1);
// replace c0 with colulmn name in the group
s = s.replaceFirst("c[0-9]+", (String) columnList.get(i));
// replace the group in the main string
sql = pm.replaceFirst(s);
// give matcher the new string
pm.reset(sql);
}

return sql;
}
}

/**
* test if can drill through this cell
* drill through is possible if the measure is a stored measure
* and not possible for calculated measures
* @return true if can drill through
*/
public boolean canDrillThrough() {
// get current members
final RolapMember[] currentMembers = getEvaluator().currentMembers;
// first member is the measure, test if it is stored measure, return true if it is, false if not
return (currentMembers[0] instanceof RolapStoredMeasure);
}

private RolapEvaluator getEvaluator() {
final int[] pos = result.getCellPos(ordinal);
Expand Down
2 changes: 1 addition & 1 deletion src/main/mondrian/rolap/agg/AggregationManager.java
Expand Up @@ -236,7 +236,7 @@ public RolapStar.Column[] getColumns() {
public Object[] getConstraints(int i) {
final Object value = request.getValueList().get(i);
if (value == null) {
return new Object[0];
return null;
} else {
return new Object[] {value};
}
Expand Down
27 changes: 19 additions & 8 deletions src/main/mondrian/test/BasicQueryTest.java
Expand Up @@ -459,9 +459,20 @@ public void testDrillThrough() {
"SELECT {[Measures].[Unit Sales], [Measures].[Price]} on columns," + nl +
" {[Product].Children} on rows" + nl +
"from Sales");
String sql = result.getCell(new int[] {0, 0}).getDrillThroughSQL();
assertEquals("select `time_by_day`.`the_year` as `c0`, `product_class`.`product_family` as `c1`, sum(`sales_fact_1997`.`unit_sales`) as `c2` from `time_by_day` as `time_by_day`, `sales_fact_1997` as `sales_fact_1997`, `product_class` as `product_class`, `product` as `product` where `sales_fact_1997`.`time_id` = `time_by_day`.`time_id` and `time_by_day`.`the_year` in (1997) and `sales_fact_1997`.`product_id` = `product`.`product_id` and `product`.`product_class_id` = `product_class`.`product_class_id` and `product_class`.`product_family` in ('Drink') group by `time_by_day`.`the_year`, `product_class`.`product_family`", sql);
sql = result.getCell(new int[] {1, 1}).getDrillThroughSQL();
String sql = result.getCell(new int[] {0, 0}).getDrillThroughSQL(false);
assertEquals("select `time_by_day`.`the_year` as `Year`, `product_class`.`product_family` as `Product Family`, sum(`sales_fact_1997`.`unit_sales`) as `Unit Sales` from `time_by_day` as `time_by_day`, `sales_fact_1997` as `sales_fact_1997`, `product_class` as `product_class`, `product` as `product` where `sales_fact_1997`.`time_id` = `time_by_day`.`time_id` and `time_by_day`.`the_year` in (1997) and `sales_fact_1997`.`product_id` = `product`.`product_id` and `product`.`product_class_id` = `product_class`.`product_class_id` and `product_class`.`product_family` in ('Drink') group by `time_by_day`.`the_year`, `product_class`.`product_family`", sql);
sql = result.getCell(new int[] {1, 1}).getDrillThroughSQL(false);
assertNull(sql); // because it is a calculated member
}
public void testDrillThrough2() {
Result result = runQuery(
"WITH MEMBER [Measures].[Price] AS '[Measures].[Store Sales] / [Measures].[Unit Sales]'" + nl +
"SELECT {[Measures].[Unit Sales], [Measures].[Price]} on columns," + nl +
" {[Product].Children} on rows" + nl +
"from Sales");
String sql = result.getCell(new int[] {0, 0}).getDrillThroughSQL(true);
assertEquals("select `store`.`store_country` as `Store Country`, `store`.`store_state` as `Store State`, `store`.`store_city` as `Store City`, `store`.`store_name` as `Store Name`, `store`.`store_sqft` as `Store Sqft`, `store`.`store_type` as `Store Type`, `time_by_day`.`the_year` as `Year`, `product_class`.`product_family` as `Product Family`, `promotion`.`media_type` as `Media Type`, `promotion`.`promotion_name` as `Promotion Name`, `customer`.`country` as `Country`, `customer`.`state_province` as `State Province`, `customer`.`city` as `City`, CONCAT(`customer`.`fname`, \" \", `customer`.`lname`) as `Name`, `customer`.`education` as `Education Level`, `customer`.`gender` as `Gender`, `customer`.`marital_status` as `Marital Status`, `customer`.`yearly_income` as `Yearly Income`, sum(`sales_fact_1997`.`unit_sales`) as `Unit Sales` from `store` as `store`, `sales_fact_1997` as `sales_fact_1997`, `time_by_day` as `time_by_day`, `product_class` as `product_class`, `product` as `product`, `promotion` as `promotion`, `customer` as `customer` where `sales_fact_1997`.`store_id` = `store`.`store_id` and `sales_fact_1997`.`store_id` = `store`.`store_id` and `sales_fact_1997`.`store_id` = `store`.`store_id` and `sales_fact_1997`.`store_id` = `store`.`store_id` and `sales_fact_1997`.`store_id` = `store`.`store_id` and `sales_fact_1997`.`store_id` = `store`.`store_id` and `sales_fact_1997`.`time_id` = `time_by_day`.`time_id` and `time_by_day`.`the_year` in (1997) and `sales_fact_1997`.`product_id` = `product`.`product_id` and `product`.`product_class_id` = `product_class`.`product_class_id` and `product_class`.`product_family` in ('Drink') and `sales_fact_1997`.`promotion_id` = `promotion`.`promotion_id` and `sales_fact_1997`.`promotion_id` = `promotion`.`promotion_id` and `sales_fact_1997`.`customer_id` = `customer`.`customer_id` and `sales_fact_1997`.`customer_id` = `customer`.`customer_id` and `sales_fact_1997`.`customer_id` = `customer`.`customer_id` and `sales_fact_1997`.`customer_id` = `customer`.`customer_id` and `sales_fact_1997`.`customer_id` = `customer`.`customer_id` and `sales_fact_1997`.`customer_id` = `customer`.`customer_id` and `sales_fact_1997`.`customer_id` = `customer`.`customer_id` and `sales_fact_1997`.`customer_id` = `customer`.`customer_id` group by `store`.`store_country`, `store`.`store_state`, `store`.`store_city`, `store`.`store_name`, `store`.`store_sqft`, `store`.`store_type`, `time_by_day`.`the_year`, `product_class`.`product_family`, `promotion`.`media_type`, `promotion`.`promotion_name`, `customer`.`country`, `customer`.`state_province`, `customer`.`city`, CONCAT(`customer`.`fname`, \" \", `customer`.`lname`), `customer`.`education`, `customer`.`gender`, `customer`.`marital_status`, `customer`.`yearly_income`", sql);
sql = result.getCell(new int[] {1, 1}).getDrillThroughSQL(true);
assertNull(sql); // because it is a calculated member
}

Expand Down Expand Up @@ -2288,11 +2299,11 @@ public void testBasketAnalysis() {
* substrings in strings or for supporting case-insensitive string
* comparisons. However, since MDX can take advantage of external function
* libraries, this question is easily resolved using string manipulation and
* comparison functions from the Microsoft Visual Basic® for Applications
* comparison functions from the Microsoft Visual Basic? for Applications
* (VBA) external function library.
*
* <p>For example, you want to report the unit sales of all fruit-based
* products—not only the sales of fruit, but canned fruit, fruit snacks,
* products?not only the sales of fruit, but canned fruit, fruit snacks,
* fruit juices, and so on. By using the LCase and InStr VBA functions, the
* following results are easily accomplished in a single MDX query, without
* complex set construction or explicit member names within the query.
Expand Down Expand Up @@ -2403,7 +2414,7 @@ public void testPercentagesAsMeasures() {
*
* <p>As an aside, a named set cannot be used in this situation to replace
* the duplicate Order function calls. Named sets are evaluated once, when a
* query is parsed—since the set can change based on the fact that the set
* query is parsed?since the set can change based on the fact that the set
* can be different for each store member because the set is evaluated for
* the children of multiple parents, the set does not change with respect to
* its use in the Sum function. Since the named set is only evaluated once,
Expand Down Expand Up @@ -2548,7 +2559,7 @@ public void _testSet() {
*
* <p>Member properties are a good way of adding secondary business
* information to members in a dimension. However, getting that information
* out can be confusing—member properties are not readily apparent in a
* out can be confusing?member properties are not readily apparent in a
* typical MDX query.
*
* <p>Member properties can be retrieved in one of two ways. The easiest
Expand Down Expand Up @@ -3105,7 +3116,7 @@ public void _testRolling() {
* forecast of warehouse sales, from the Warehouse cube in the FoodMart 2000
* database, for drink products. The standard forecast is double the
* warehouse sales of the previous year, while the dynamic forecast varies
* from month to month—the forecast for January is 120 percent of previous
* from month to month?the forecast for January is 120 percent of previous
* sales, while the forecast for July is 260 percent of previous sales.
*
* <p>The most flexible way of handling this type of report is the use of
Expand Down

0 comments on commit 866314d

Please sign in to comment.