Skip to content

Commit

Permalink
MONDRIAN - added support for specifying an expression for a measure
Browse files Browse the repository at this point in the history
[git-p4: depot-paths = "//open/mondrian/": change = 6982]
  • Loading branch information
Zelaine Fong committed Jun 22, 2006
1 parent 50fd4f4 commit a15cc4b
Show file tree
Hide file tree
Showing 13 changed files with 243 additions and 10 deletions.
19 changes: 19 additions & 0 deletions demo/FoodMart.xml
Expand Up @@ -268,6 +268,25 @@ fullname
formatString="#,###"/>
<Measure name="Customer Count" column="customer_id"
aggregator="distinct count" formatString="#,###"/>
<Measure name="Promotion Sales" aggregator="sum" formatString="#,###.00">
<MeasureExpression>
<SQL dialect="oracle">
(case when "sales_fact_1997"."promotion_id" = 0 then 0 else "sales_fact_1997"."store_sales" end)
</SQL>
<SQL dialect="postgres">
(case when "sales_fact_1997"."promotion_id" = 0 then 0 else "sales_fact_1997"."store_sales" end)
</SQL>
<SQL dialect="mysql">
(case when `sales_fact_1997`.`promotion_id` = 0 then 0 else `sales_fact_1997`.`store_sales` end)
</SQL>
<SQL dialect="derby">
(case when "sales_fact_1997"."promotion_id" = 0 then 0 else "sales_fact_1997"."store_sales" end)
</SQL>
<SQL dialect="generic">
(case when sales_fact_1997.promotion_id = 0 then 0 else sales_fact_1997.store_sales end)
</SQL>
</MeasureExpression>
</Measure>
<CalculatedMember
name="Profit"
dimension="Measures">
Expand Down
14 changes: 12 additions & 2 deletions src/main/mondrian/olap/Mondrian.xml
Expand Up @@ -691,8 +691,11 @@ Revision is $Id$
<Attribute name="name" required="true">
<Doc>Name of this measure.</Doc>
</Attribute>
<Attribute name="column" required="true">
<Doc>Column which is source of this measure's values.</Doc>
<Attribute name="column" required="false">
<Doc>
Column which is source of this measure's values.
If not specified, a measure expression must be specified.
</Doc>
</Attribute>
<Attribute name="datatype" required="false">
<Doc>
Expand Down Expand Up @@ -739,6 +742,12 @@ Revision is $Id$
Default true.
</Doc>
</Attribute>
<Object name="measureExp" type="MeasureExpression" required="false">
<Doc>
The SQL expression used to calculate a measure.
Must be specified if a source column is not specified.
</Doc>
</Object>
<Array name="memberProperties" type="CalculatedMemberProperty" min="0"/>
</Element>

Expand Down Expand Up @@ -1495,6 +1504,7 @@ Revision is $Id$
<Element type="OrdinalExpression" class="ExpressionView"/>
<Element type="NameExpression" class="ExpressionView"/>
<Element type="CaptionExpression" class="ExpressionView"/>
<Element type="MeasureExpression" class="ExpressionView"/>

<!-- Access control =================================================== -->
<Element type="Role">
Expand Down
4 changes: 4 additions & 0 deletions src/main/mondrian/resource/MondrianResource.xml
Expand Up @@ -429,6 +429,10 @@
<text>Cube ''{0}'': Ordinal {1} is not unique: ''{2}'' and ''{3}''</text>
</exception>

<exception id="40260" name="BadMeasureSource">
<text>Cube ''{0}'': Measure ''{1}'' must contain either a source column or a source expression, but not both</text>
</exception>

<!-- ====================================================================== -->
<!-- Loader -->

Expand Down
21 changes: 17 additions & 4 deletions src/main/mondrian/rolap/RolapCube.java
Expand Up @@ -169,12 +169,25 @@ private RolapCube(RolapSchema schema,
xmlCube.measures.length];
for (int i = 0; i < xmlCube.measures.length; i++) {
MondrianDef.Measure xmlMeasure = xmlCube.measures[i];

MondrianDef.Column column =
new MondrianDef.Column(fact.getAlias(), xmlMeasure.column);
MondrianDef.Expression measureExp;
if (xmlMeasure.column != null) {
if (xmlMeasure.measureExp != null) {
throw MondrianResource.instance().
BadMeasureSource.ex(
xmlCube.name, xmlMeasure.name);
}
measureExp = new MondrianDef.Column(
fact.getAlias(), xmlMeasure.column);
} else if (xmlMeasure.measureExp != null) {
measureExp = xmlMeasure.measureExp;
} else {
throw MondrianResource.instance().
BadMeasureSource.ex(
xmlCube.name, xmlMeasure.name);
}
final RolapStoredMeasure measure = new RolapStoredMeasure(
this, null, measuresLevel, xmlMeasure.name,
xmlMeasure.formatString, column,
xmlMeasure.formatString, measureExp,
xmlMeasure.aggregator, xmlMeasure.datatype);
measures[i] = measure;

Expand Down
27 changes: 24 additions & 3 deletions testsrc/main/mondrian/olap/fun/FunctionTest.java
Expand Up @@ -681,7 +681,8 @@ public void testMembers() {
"[Measures].[Store Cost]",
"[Measures].[Store Sales]",
"[Measures].[Sales Count]",
"[Measures].[Customer Count]"
"[Measures].[Customer Count]",
"[Measures].[Promotion Sales]"
}));

// <Dimension>.members applied to Measures
Expand All @@ -691,7 +692,8 @@ public void testMembers() {
"[Measures].[Store Cost]",
"[Measures].[Store Sales]",
"[Measures].[Sales Count]",
"[Measures].[Customer Count]"}));
"[Measures].[Customer Count]",
"[Measures].[Promotion Sales]"}));

// <Dimension>.members applied to a query with calc measures
// Again, no calc measures are returned
Expand All @@ -706,11 +708,13 @@ public void testMembers() {
"{[Measures].[Store Sales]}",
"{[Measures].[Sales Count]}",
"{[Measures].[Customer Count]}",
"{[Measures].[Promotion Sales]}",
"Row #0: 266,773",
"Row #0: 225,627.23",
"Row #0: 565,238.13",
"Row #0: 86,837",
"Row #0: 5,581",
"Row #0: 151,211.21",
""}));
}

Expand All @@ -736,6 +740,7 @@ public void testAllMembers() {
"[Measures].[Store Sales]",
"[Measures].[Sales Count]",
"[Measures].[Customer Count]",
"[Measures].[Promotion Sales]",
"[Measures].[Profit]",
"[Measures].[Profit last Period]",
"[Measures].[Profit Growth]"}));
Expand All @@ -748,6 +753,7 @@ public void testAllMembers() {
"[Measures].[Store Sales]",
"[Measures].[Sales Count]",
"[Measures].[Customer Count]",
"[Measures].[Promotion Sales]",
"[Measures].[Profit]",
"[Measures].[Profit last Period]",
"[Measures].[Profit Growth]"}));
Expand All @@ -765,6 +771,7 @@ public void testAllMembers() {
"{[Measures].[Store Sales]}",
"{[Measures].[Sales Count]}",
"{[Measures].[Customer Count]}",
"{[Measures].[Promotion Sales]}",
"{[Measures].[Profit]}",
"{[Measures].[Profit last Period]}",
"{[Measures].[Profit Growth]}",
Expand All @@ -774,6 +781,7 @@ public void testAllMembers() {
"Row #0: 565,238.13",
"Row #0: 86,837",
"Row #0: 5,581",
"Row #0: 151,211.21",
"Row #0: $339,610.90",
"Row #0: $339,610.90",
"Row #0: 0.0%",
Expand All @@ -795,6 +803,7 @@ public void testAllMembers() {
"{[Measures].[Store Sales]}",
"{[Measures].[Sales Count]}",
"{[Measures].[Customer Count]}",
"{[Measures].[Promotion Sales]}",
"{[Measures].[Profit]}",
"{[Measures].[Profit last Period]}",
"{[Measures].[Profit Growth]}",
Expand All @@ -808,6 +817,7 @@ public void testAllMembers() {
"Row #0: 36,175.20",
"Row #0: 5,498",
"Row #0: 1,110",
"Row #0: 14,447.16",
"Row #0: $21,744.11",
"Row #0: $21,744.11",
"Row #0: 0.0%",
Expand All @@ -817,6 +827,7 @@ public void testAllMembers() {
"Row #1: 40,170.29",
"Row #1: 6,184",
"Row #1: 767",
"Row #1: 10,829.64",
"Row #1: $24,089.22",
"Row #1: $24,089.22",
"Row #1: 0.0%",
Expand All @@ -826,6 +837,7 @@ public void testAllMembers() {
"Row #2: 63,282.86",
"Row #2: 9,906",
"Row #2: 1,104",
"Row #2: 18,459.60",
"Row #2: $38,042.78",
"Row #2: $38,042.78",
"Row #2: 0.0%",
Expand All @@ -847,6 +859,7 @@ public void testAllMembers() {
"{[Measures].[Store Sales]}",
"{[Measures].[Sales Count]}",
"{[Measures].[Customer Count]}",
"{[Measures].[Promotion Sales]}",
"{[Measures].[Profit]}",
"{[Measures].[Profit last Period]}",
"{[Measures].[Profit Growth]}",
Expand All @@ -860,6 +873,7 @@ public void testAllMembers() {
"Row #0: 36,175.20",
"Row #0: 5,498",
"Row #0: 1,110",
"Row #0: 14,447.16",
"Row #0: $21,744.11",
"Row #0: $21,744.11",
"Row #0: 0.0%",
Expand All @@ -869,6 +883,7 @@ public void testAllMembers() {
"Row #1: 40,170.29",
"Row #1: 6,184",
"Row #1: 767",
"Row #1: 10,829.64",
"Row #1: $24,089.22",
"Row #1: $24,089.22",
"Row #1: 0.0%",
Expand All @@ -878,6 +893,7 @@ public void testAllMembers() {
"Row #2: 63,282.86",
"Row #2: 9,906",
"Row #2: 1,104",
"Row #2: 18,459.60",
"Row #2: $38,042.78",
"Row #2: $38,042.78",
"Row #2: 0.0%",
Expand All @@ -899,6 +915,7 @@ public void testAllMembers() {
"{[Measures].[Store Sales]}",
"{[Measures].[Sales Count]}",
"{[Measures].[Customer Count]}",
"{[Measures].[Promotion Sales]}",
"Axis #2:",
"{[Store].[All Stores].[USA].[CA]}",
"{[Store].[All Stores].[USA].[OR]}",
Expand All @@ -908,16 +925,19 @@ public void testAllMembers() {
"Row #0: 36,175.20",
"Row #0: 5,498",
"Row #0: 1,110",
"Row #0: 14,447.16",
"Row #1: 19,287",
"Row #1: 16,081.07",
"Row #1: 40,170.29",
"Row #1: 6,184",
"Row #1: 767",
"Row #1: 10,829.64",
"Row #2: 30,114",
"Row #2: 25,240.08",
"Row #2: 63,282.86",
"Row #2: 9,906",
"Row #2: 1,104",
"Row #2: 18,459.60",
""}));

// Calc member in dimension based on level
Expand Down Expand Up @@ -1080,7 +1100,8 @@ public void testStripCalculatedMembers() {
"[Measures].[Store Cost]",
"[Measures].[Store Sales]",
"[Measures].[Sales Count]",
"[Measures].[Customer Count]"}));
"[Measures].[Customer Count]",
"[Measures].[Promotion Sales]"}));

// applied to empty set
assertAxisReturns("StripCalculatedMembers({[Gender].Parent})", "");
Expand Down
65 changes: 65 additions & 0 deletions testsrc/main/mondrian/test/BasicQueryTest.java
Expand Up @@ -416,6 +416,17 @@ public BasicQueryTest(String name) {
"Row #10: 508,272.49\n" +
"Row #11: 56,965.64\n" +
"Row #11: 565,238.13\n"),

// 8
new QueryAndResult(
"select {[Measures].[Promotion Sales]} on columns\n" +
" from Sales",

"Axis #0:\n" +
"{}\n" +
"Axis #1:\n" +
"{[Measures].[Promotion Sales]}\n" +
"Row #0: 151,211.21\n"),
};

public void testSample0() {
Expand Down Expand Up @@ -449,6 +460,10 @@ public void testSample6() {
public void testSample7() {
assertQueryReturns(sampleQueries[7].query, sampleQueries[7].result);
}

public void testSample8() {
assertQueryReturns(sampleQueries[8].query, sampleQueries[8].result);
}

public void testGoodComments() {

Expand Down Expand Up @@ -5113,6 +5128,56 @@ public void testOverrideDimension() {
"Row #0: 66,291\n" +
"Row #0: 66,291\n"));
}

public void testBadMeasure1() {
Schema schema = getConnection().getSchema();
final String cubeName = "SalesWithBadMeasure";
Throwable throwable = null;
try {
schema.createCube(
"<Cube name=\"" + cubeName + "\">\n" +
" <Table name=\"sales_fact_1997\"/>\n" +
" <DimensionUsage name=\"Time\" source=\"Time\" foreignKey=\"time_id\"/>\n" +
" <Measure name=\"Bad Measure\" aggregator=\"sum\"\n" +
" formatString=\"Standard\"/>\n" +
"</Cube>");
} catch (Throwable e) {
throwable = e;
}
// neither a source column or source expression specified
TestContext.checkThrowable(
throwable,
"must contain either a source column or a source expression, but not both");
schema.removeCube(cubeName);
}

public void testBadMeasure2() {
Schema schema = getConnection().getSchema();
final String cubeName = "SalesWithBadMeasure";
Throwable throwable = null;
try {
schema.createCube(
"<Cube name=\"" + cubeName + "\">\n" +
" <Table name=\"sales_fact_1997\"/>\n" +
" <DimensionUsage name=\"Time\" source=\"Time\" foreignKey=\"time_id\"/>\n" +
" <Measure name=\"Bad Measure\" column=\"unit_sales\" aggregator=\"sum\"\n" +
" formatString=\"Standard\">\n" +
" <MeasureExpression>\n" +
" <SQL dialect=\"generic\">\n" +
" unit_sales\n" +
" </SQL>\n" +
" </MeasureExpression>\n" +
" </Measure>\n" +
"</Cube>");
} catch (Throwable e) {
throwable = e;
}
// both a source column and source expression specified
TestContext.checkThrowable(
throwable,
"must contain either a source column or a source expression, but not both");
schema.removeCube(cubeName);
}
}

// End BasicQueryTest.java
28 changes: 28 additions & 0 deletions testsrc/main/mondrian/test/DrillThroughTest.java
Expand Up @@ -224,6 +224,34 @@ public void testDrillThroughBug1472311() {
"and `sales_fact_1997`.`customer_id` = `customer`.`customer_id`";
assertSqlEquals(expectedSql, sql);
}

// Test that proper SQL is being generated for a Measure specified
// as an expression
public void testDrillThroughMeasureExp() {
Result result = executeQuery(
"SELECT {[Measures].[Promotion Sales]} on columns," + nl +
" {[Product].Children} on rows" + nl +
"from Sales");
String sql = result.getCell(new int[] {0, 0}).getDrillThroughSQL(false);

String expectedSql =
"select `time_by_day`.`the_year` as `Year`," +
" `product_class`.`product_family` as `Product Family`," +
" (case when `sales_fact_1997`.`promotion_id` = 0 then 0" +
" else `sales_fact_1997`.`store_sales` end)" +
" as `Promotion 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` = 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` = 'Drink'";

assertSqlEquals(expectedSql, sql);
}
}

// End DrillThroughTest.java
2 changes: 1 addition & 1 deletion testsrc/main/mondrian/test/TestContext.java
Expand Up @@ -389,7 +389,7 @@ public void assertAxisThrows(
checkThrowable(throwable, pattern);
}

private static void checkThrowable(Throwable throwable, String pattern) {
public static void checkThrowable(Throwable throwable, String pattern) {
if (throwable == null) {
Assert.fail("query did not yield an exception");
}
Expand Down

0 comments on commit a15cc4b

Please sign in to comment.